diff --git a/lib/vagrant.rb b/lib/vagrant.rb index 4fb6c5669e5..ad860e3931c 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -140,6 +140,8 @@ def << msg rescue => err global_logger.warn("unexpected failure loading ssl providers, attempting to continue (#{err})") end +else + global_logger.warn("vagrant ssl helper was not found, continuing...") end # We need these components always so instead of an autoload we diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index d74e5d29af2..f04b8b86127 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -68,17 +68,29 @@ def call(env) # If we received a shorthand URL ("mitchellh/precise64"), # then expand it properly. expanded = false - url.each_index do |i| - next if url[i] !~ /^[^\/]+\/[^\/]+$/ + # Mark if only a single url entry was provided + single_entry = url.size == 1 - if !File.file?(url[i]) + url = url.map do |url_entry| + if url_entry =~ /^[^\/]+\/[^\/]+$/ && !File.file?(url_entry) server = Vagrant.server_url env[:box_server_url] raise Errors::BoxServerNotSet if !server expanded = true - url[i] = "#{server}/#{url[i]}" + # If only a single entry, expand to both the API endpoint and + # the direct shorthand endpoint. + if single_entry + url_entry = [ + "#{server}/api/v2/vagrant/#{url_entry}", + "#{server}/#{url_entry}" + ] + else + url_entry = "#{server}/#{url_entry}" + end end - end + + url_entry + end.flatten # Call the hook to transform URLs into authenticated URLs. # In the case we don't have a plugin that does this, then it @@ -99,6 +111,21 @@ def call(env) end end + # If only a single entry was provided, and it was expanded, + # inspect the metadata check results and extract the one that + # was successful, with preference to the API endpoint + if single_entry && expanded + idx = is_metadata_results.index { |v| v === true } + # If none of the urls were successful, set the index + # as the last entry + idx = is_metadata_results.size - 1 if idx.nil? + + # Now reset collections with single value + is_metadata_results = [is_metadata_results[idx]] + authed_urls = [authed_urls[idx]] + url = [url[idx]] + end + if expanded && url.length == 1 is_error = is_metadata_results.find do |b| b.is_a?(Errors::DownloaderError) @@ -165,6 +192,7 @@ def add_direct(urls, env) env, checksum: env[:box_checksum], checksum_type: env[:box_checksum_type], + architecture: env[:architecture] ) end @@ -179,6 +207,9 @@ def add_direct(urls, env) # 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 +259,17 @@ def add_from_metadata(url, env, expanded) 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 +285,16 @@ def add_from_metadata(url, env, expanded) # 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 +315,7 @@ def add_from_metadata(url, env, expanded) end metadata_provider = metadata_version.provider( - providers[choice-1]) + providers[choice-1], architecture) end provider_url = metadata_provider.url @@ -293,6 +329,17 @@ def add_from_metadata(url, env, expanded) provider_url = authed_urls[0] end + # The architecture name used when adding the box should be + # the value extracted from the metadata provider + arch_name = metadata_provider.architecture + + # In the special case where the architecture name is "unknown" and + # it is listed as the default architecture, unset the architecture + # name so it is installed without architecture information + if arch_name == "unknown" && metadata_provider.default_architecture + arch_name = nil + end + box_add( [[provider_url, metadata_provider.url]], metadata.name, @@ -302,6 +349,7 @@ def add_from_metadata(url, env, expanded) env, checksum: metadata_provider.checksum, checksum_type: metadata_provider.checksum_type, + architecture: arch_name, ) end @@ -317,16 +365,21 @@ def add_from_metadata(url, env, expanded) # @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 +430,9 @@ def box_add(urls, name, version, provider, md_url, env, **opts) box_url, name, version, force: env[:box_force], metadata_url: md_url, - providers: provider) + providers: provider, + architecture: opts[: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 +451,10 @@ def box_add(urls, name, version, provider, md_url, env, **opts) "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 diff --git a/lib/vagrant/action/builtin/box_remove.rb b/lib/vagrant/action/builtin/box_remove.rb index fb834f25a7f..f103284076a 100644 --- a/lib/vagrant/action/builtin/box_remove.rb +++ b/lib/vagrant/action/builtin/box_remove.rb @@ -15,112 +15,162 @@ def initialize(app, env) 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] - - boxes = {} - env[:box_collection].all.each do |n, v, p| - boxes[n] ||= {} - boxes[n][p] ||= [] - boxes[n][p] << v + box_remove_all_providers = env[:box_remove_all_providers] + box_remove_all_architectures = env[:box_remove_all_architectures] + + 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, + # 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, - providers: all_box.keys.map(&:to_s).sort.join(", ") + versions: box_info.keys.sort.map { |k| " * #{k}" }.join("\n") 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. - raise Errors::BoxRemoveMultiVersion, - name: box_name, - provider: box_provider.to_s, - versions: all_versions.sort.map { |k| " * #{k}" }.join("\n") + # 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 - 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 - - 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") - - 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 + # 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 - # Keep used boxes, even if "force" is applied - keep_used_boxes = env[:keep_used_boxes] + # 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 - result = env[:action_runner].run(stack, env) - if !result[:result] || keep_used_boxes - # They said "no", so continue with the next box - next + # 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 + 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 + 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 + ) + + # 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) + " " + + # 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] + + 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 + + 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 end @app.call(env) diff --git a/lib/vagrant/action/builtin/handle_box.rb b/lib/vagrant/action/builtin/handle_box.rb index d8964f387d8..c2864098186 100644 --- a/lib/vagrant/action/builtin/handle_box.rb +++ b/lib/vagrant/action/builtin/handle_box.rb @@ -87,6 +87,7 @@ def handle_box(env) 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, diff --git a/lib/vagrant/box.rb b/lib/vagrant/box.rb index c5bcc5d25f2..3f636a9e465 100644 --- a/lib/vagrant/box.rb +++ b/lib/vagrant/box.rb @@ -37,6 +37,11 @@ class Box # @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 @@ class Box # @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 @@ def in_use?(index) # 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 @@ def has_update?(version=nil, download_options: {}) 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 @@ def repackage(path) 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 diff --git a/lib/vagrant/box_collection.rb b/lib/vagrant/box_collection.rb index 2e09bc1c12f..117d31f3abd 100644 --- a/lib/vagrant/box_collection.rb +++ b/lib/vagrant/box_collection.rb @@ -28,7 +28,7 @@ class BoxCollection # 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 @@ class BoxCollection # 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 @@ def initialize(directory, options=nil) # @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 @@ def add(path, name, version, **opts) 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 @@ def add(path, name, version, **opts) 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,19 +170,26 @@ def add(path, name, version, **opts) @logger.debug("Box directory: #{box_dir}") # This is the final directory we'll move it to - final_dir = box_dir.join(provider.to_s) - if final_dir.exist? + if architecture + arch = architecture + arch = Util::Platform.architecture if architecture == :auto + box_dir = box_dir.join(arch) + end + provider_dir = box_dir.join(provider.to_s) + @logger.debug("Provider directory: #{provider_dir}") + + if provider_dir.exist? @logger.debug("Removing existing provider directory...") - final_dir.rmtree + provider_dir.rmtree end # Move to final destination - final_dir.mkpath + provider_dir.mkpath # Recursively move individual files from the temporary directory # to the final location. We do this instead of moving the entire # directory to avoid issues on Windows. [GH-1424] - copy_pairs = [[final_temp_dir, final_dir]] + copy_pairs = [[final_temp_dir, provider_dir]] while !copy_pairs.empty? from, to = copy_pairs.shift from.children(true).each do |f| @@ -209,13 +219,13 @@ def add(path, name, version, **opts) 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 = [] @@ -236,25 +246,52 @@ def all next if versiondir.basename.to_s.start_with?(".") version = versiondir.basename.to_s + # Ensure version of box is correct before continuing + if !Gem::Version.correct?(version) + ui = Vagrant::UI::Prefixed.new(Vagrant::UI::Colored.new, "vagrant") + ui.warn(I18n.t("vagrant.box_version_malformed", + version: version, box_name: box_name)) + @logger.debug("Invalid version #{version} for box #{box_name}") + next + end - versiondir.children(true).each do |provider| - # Ensure version of box is correct before continuing - if !Gem::Version.correct?(version) - ui = Vagrant::UI::Prefixed.new(Vagrant::UI::Colored.new, "vagrant") - ui.warn(I18n.t("vagrant.box_version_malformed", - version: version, box_name: box_name)) - @logger.debug("Invalid version #{version} for box #{box_name}") + versiondir.children(true).each do |architecture_or_provider| + # If the entry is not a directory, it is invalid and should be ignored + if !architecture_or_provider.directory? + @logger.debug("Invalid box #{box_name} (v#{version}) - invalid item: #{architecture_or_provider}") next end - # Verify this is a potentially valid box. If it looks - # correct enough then include it. - if provider.directory? && provider.join("metadata.json").file? - provider_name = provider.basename.to_s.to_sym + # Now the directory can be assumed to be the architecture + architecture_name = architecture_or_provider.basename.to_s.to_sym + + # Cycle through directories to find providers + architecture_or_provider.children(true).each do |provider| + if !provider.directory? + @logger.debug("Invalid box #{box_name} (v#{version}, #{architecture_name}) - invalid item: #{provider}") + next + end + + # If the entry contains a metadata file, add it + if provider.join("metadata.json").file? + provider_name = provider.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 + + # If the base entry contains a metadata file, then it was + # added prior to architecture support and is a provider directory. + # If it contains a metadata file, include it with the results only + # if an entry hasn't already been included for the local system's + # architecture + if architecture_or_provider.join("metadata.json").file? + provider_name = architecture_or_provider.basename.to_s.to_sym + if results.include?([box_name, version, provider_name, Util::Platform.architecture.to_sym]) + next + end @logger.debug("Box: #{box_name} (#{provider_name}, #{version})") - results << [box_name, version, provider_name] - else - @logger.debug("Invalid box #{box_name}, ignoring: #{provider}") + results << [box_name, version, provider_name, nil] end end end @@ -262,7 +299,7 @@ def all 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 +311,10 @@ def all # @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| @@ -318,9 +357,42 @@ def find(name, providers, version) end versiondir = box_directory.join(version_dir_map[v.to_s]) + providers.each do |provider| - provider_dir = versiondir.join(provider.to_s) - next if !provider_dir.directory? + providerdir = versiondir.join(architecture.to_s).join(provider.to_s) + + # If the architecture was automatically set to the host + # architecture, then a match on the architecture subdirectory + # or the provider directory (which is a box install prior to + # architecture support) is valid + if box_architecture == :auto + if !providerdir.directory? + providerdir = versiondir.join(provider.to_s) + end + + if providerdir.join("metadata.json").file? + @logger.info("Box found: #{name} (#{provider})") + + metadata_url = nil + metadata_url_file = box_directory.join("metadata_url") + metadata_url = metadata_url_file.read if metadata_url_file.file? + + if metadata_url && @hook + hook_env = @hook.call( + :authenticate_box_url, box_urls: [metadata_url]) + metadata_url = hook_env[:box_urls].first + end + + return Box.new( + name, provider, version_dir_map[v.to_s], providerdir, + architecture: architecture, metadata_url: metadata_url, hook: @hook + ) + end + end + + # If there is no metadata file found, skip + next if !providerdir.join("metadata.json").file? + @logger.info("Box found: #{name} (#{provider})") metadata_url = nil @@ -334,8 +406,8 @@ def find(name, providers, version) 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], providerdir, + architecture: architecture, metadata_url: metadata_url, hook: @hook ) end end diff --git a/lib/vagrant/box_metadata.rb b/lib/vagrant/box_metadata.rb index fc4a86a165a..b66580cb7d7 100644 --- a/lib/vagrant/box_metadata.rb +++ b/lib/vagrant/box_metadata.rb @@ -38,7 +38,7 @@ def initialize(io, **_) @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 @@ def version(version, **opts) 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 @@ def version(version, **opts) # # @return[Array] 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 @@ def initialize(raw=nil, **_) 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] - 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 @@ class Provider # @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 diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 04354be67ff..d0df2f08f62 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -199,6 +199,10 @@ class BoxNotFoundWithProvider < VagrantError 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 @@ class BoxRemoveNotFound < VagrantError 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 @@ class BoxRemoveVersionNotFound < VagrantError 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 @@ class BoxUpdateMultiProvider < VagrantError 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 diff --git a/lib/vagrant/machine.rb b/lib/vagrant/machine.rb index 163638fe088..7da6bb36593 100644 --- a/lib/vagrant/machine.rb +++ b/lib/vagrant/machine.rb @@ -326,6 +326,7 @@ def id=(value) 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 @@ def id=(value) 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 @@ def recover_machine(state) entry.extra_data["box"] = { "name" => @box.name, "provider" => @box.provider.to_s, + "architecture" => @box.architecture, "version" => @box.version.to_s, } end diff --git a/lib/vagrant/machine_index.rb b/lib/vagrant/machine_index.rb index c07919d0885..188667aaa11 100644 --- a/lib/vagrant/machine_index.rb +++ b/lib/vagrant/machine_index.rb @@ -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 @@ class Entry # @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 @@ def initialize(id=nil, raw=nil) @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 @@ def to_json_struct "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, diff --git a/lib/vagrant/util.rb b/lib/vagrant/util.rb index af978a0988b..1f799af3b69 100644 --- a/lib/vagrant/util.rb +++ b/lib/vagrant/util.rb @@ -46,7 +46,7 @@ module Remote 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' diff --git a/lib/vagrant/util/platform.rb b/lib/vagrant/util/platform.rb index 0adf8a81720..03f25db55d8 100644 --- a/lib/vagrant/util/platform.rb +++ b/lib/vagrant/util/platform.rb @@ -17,6 +17,29 @@ module Util 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") diff --git a/lib/vagrant/vagrantfile.rb b/lib/vagrant/vagrantfile.rb index 69d861ab142..d663653c6eb 100644 --- a/lib/vagrant/vagrantfile.rb +++ b/lib/vagrant/vagrantfile.rb @@ -202,7 +202,7 @@ def machine_config(name, provider, boxes, data_path=nil, validate_provider=true) # 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 diff --git a/plugins/commands/box/command/add.rb b/plugins/commands/box/command/add.rb index d3d2ab8bff4..9d33b27c6a3 100644 --- a/plugins/commands/box/command/add.rb +++ b/plugins/commands/box/command/add.rb @@ -12,7 +12,9 @@ class Add < Vagrant.plugin("2", :command) include DownloadMixins def execute - options = {} + options = { + architecture: :auto, + } opts = OptionParser.new do |o| o.banner = "Usage: vagrant box add [options] " @@ -34,6 +36,10 @@ def execute 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 @@ def execute 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], diff --git a/plugins/commands/box/command/list.rb b/plugins/commands/box/command/list.rb index d80698579c4..d55c138b643 100644 --- a/plugins/commands/box/command/list.rb +++ b/plugins/commands/box/command/list.rb @@ -44,26 +44,33 @@ def list_boxes(boxes, extra_info) 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})") - - @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) + # 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) - 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 diff --git a/plugins/commands/box/command/remove.rb b/plugins/commands/box/command/remove.rb index cf320b03b77..9a8b88aed61 100644 --- a/plugins/commands/box/command/remove.rb +++ b/plugins/commands/box/command/remove.rb @@ -21,6 +21,9 @@ def execute 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 @@ def execute 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 @@ def execute @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 diff --git a/plugins/commands/box/command/update.rb b/plugins/commands/box/command/update.rb index 6a569bde8ca..34312cc5133 100644 --- a/plugins/commands/box/command/update.rb +++ b/plugins/commands/box/command/update.rb @@ -32,6 +32,10 @@ def execute 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 @@ def execute 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 @@ def execute 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] + + 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 - to_update.each do |n, p, v| - box = @env.boxes.find(n, p, v) - box_update(box, "> #{v}", @env.ui, download_options, force) + 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 @@ def box_update(box, version, ui, download_options, force) 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 @@ def box_update(box, version, ui, download_options, force) 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], diff --git a/plugins/commands/cloud/box/show.rb b/plugins/commands/cloud/box/show.rb index 35fdb8c4821..101387ab6df 100644 --- a/plugins/commands/cloud/box/show.rb +++ b/plugins/commands/cloud/box/show.rb @@ -11,7 +11,12 @@ class Show < Vagrant.plugin("2", :command) 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 @@ def execute 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 @@ def execute @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 @@ def show_box(org, box_name, access_token, options={}) 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 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 !box.empty? - box.each do |b| - format_box_results(b, @env) + 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 diff --git a/plugins/commands/cloud/client/client.rb b/plugins/commands/cloud/client/client.rb index ff44e707c8f..1e7fec351e9 100644 --- a/plugins/commands/cloud/client/client.rb +++ b/plugins/commands/cloud/client/client.rb @@ -168,6 +168,11 @@ def token 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 diff --git a/plugins/commands/cloud/locales/en.yml b/plugins/commands/cloud/locales/en.yml index acdae7a04c9..81c8c2461fb 100644 --- a/plugins/commands/cloud/locales/en.yml +++ b/plugins/commands/cloud/locales/en.yml @@ -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} diff --git a/plugins/commands/cloud/provider/create.rb b/plugins/commands/cloud/provider/create.rb index e41bc98388d..30c55c7d610 100644 --- a/plugins/commands/cloud/provider/create.rb +++ b/plugins/commands/cloud/provider/create.rb @@ -11,7 +11,9 @@ class Create < Vagrant.plugin("2", :command) 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 @@ def execute 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 @@ def execute # @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 @@ def create_provider(org, box, version, provider, url, access_token, options={}) 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 diff --git a/plugins/commands/cloud/provider/delete.rb b/plugins/commands/cloud/provider/delete.rb index e7a430ad3f2..f80ebff32b3 100644 --- a/plugins/commands/cloud/provider/delete.rb +++ b/plugins/commands/cloud/provider/delete.rb @@ -14,7 +14,7 @@ def execute 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 @@ def execute # 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 @@ def execute 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 @@ def execute # @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 diff --git a/plugins/commands/cloud/provider/update.rb b/plugins/commands/cloud/provider/update.rb index 894089627c3..22bb6b75bd7 100644 --- a/plugins/commands/cloud/provider/update.rb +++ b/plugins/commands/cloud/provider/update.rb @@ -14,25 +14,31 @@ def execute 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 @@ def execute 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 @@ def execute # @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 @@ def update_provider(org, box, version, provider, url, access_token, options) 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 diff --git a/plugins/commands/cloud/publish.rb b/plugins/commands/cloud/publish.rb index 8409363f200..a8e9c71b651 100644 --- a/plugins/commands/cloud/publish.rb +++ b/plugins/commands/cloud/publish.rb @@ -11,7 +11,10 @@ class Publish < Vagrant.plugin("2", :command) 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 @@ def execute 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 @@ def execute 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 @@ def execute 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 @@ def execute @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 @@ def execute # 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 @@ def execute # 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 @@ def set_version_info(version, options={}) # # @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 @@ def set_provider_info(provider, options={}) # @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 @@ def load_box(org, box_name, access_token) # @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 @@ def display_preamble(org, box_name, version, provider_name, options={}) 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", diff --git a/plugins/commands/cloud/search.rb b/plugins/commands/cloud/search.rb index 7fd7541cf77..a18793b2b12 100644 --- a/plugins/commands/cloud/search.rb +++ b/plugins/commands/cloud/search.rb @@ -21,6 +21,9 @@ def execute 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 @@ def search(query, access_token, options={}) 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? diff --git a/plugins/commands/cloud/util.rb b/plugins/commands/cloud/util.rb index 011800a6355..eeaf8338b01 100644 --- a/plugins/commands/cloud/util.rb +++ b/plugins/commands/cloud/util.rb @@ -102,7 +102,8 @@ def format_search_results(search_results, short, json, env) 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 @@ def format_search_results(search_results, short, json, env) # @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 @@ def with_version(account:, org:, box:, version:) # @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 @@ def with_provider(account:, org:, box:, version:, provider:) # # @param [VagrantCloud::Box] box Box for extracting information # @return [Hash] - 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 @@ def box_info(box) # @param [VagrantCloud::Box::Version] version Box version for extracting information # @return [Hash] 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] + 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 diff --git a/plugins/kernel_v2/config/vm.rb b/plugins/kernel_v2/config/vm.rb index e0c4fe740be..3c342a5166f 100644 --- a/plugins/kernel_v2/config/vm.rb +++ b/plugins/kernel_v2/config/vm.rb @@ -32,6 +32,7 @@ class VMConfig < Vagrant.plugin("2", :config) 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 @@ def initialize @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 @@ def finalize! @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 diff --git a/tasks/test.rake b/tasks/test.rake index c47aac691b2..6fae3cd49aa 100644 --- a/tasks/test.rake +++ b/tasks/test.rake @@ -4,6 +4,5 @@ require 'rspec/core/rake_task' namespace :test do RSpec::Core::RakeTask.new(:unit) do |t| t.pattern = "test/unit/**/*_test.rb" - t.rspec_opts = "--color --format documentation" end end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index c6857c8799f..a66eabfaea1 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -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} diff --git a/test/unit/base.rb b/test/unit/base.rb index 3a9e3984eb3..2f94851820e 100644 --- a/test/unit/base.rb +++ b/test/unit/base.rb @@ -41,7 +41,7 @@ # Configure RSpec RSpec.configure do |c| - #c.formatter = :progress + c.formatter = :progress c.color_mode = :on if Vagrant::Util::Platform.windows? diff --git a/test/unit/plugins/commands/box/command/add_test.rb b/test/unit/plugins/commands/box/command/add_test.rb index 6ce467c6f3a..a3d504ec87c 100644 --- a/test/unit/plugins/commands/box/command/add_test.rb +++ b/test/unit/plugins/commands/box/command/add_test.rb @@ -67,4 +67,16 @@ to raise_error(Vagrant::Errors::CLIInvalidUsage) end end + + context "with architecture flag" do + let(:argv) { ["foo", "--architecture", "test-arch"] } + + it "executes the runner with box architecture set" do + expect(action_runner).to receive(:run) do |_, opts| + expect(opts[:box_architecture]).to eq("test-arch") + end + + subject.execute + end + end end diff --git a/test/unit/plugins/commands/box/command/remove_test.rb b/test/unit/plugins/commands/box/command/remove_test.rb index afefe49752b..d1124863162 100644 --- a/test/unit/plugins/commands/box/command/remove_test.rb +++ b/test/unit/plugins/commands/box/command/remove_test.rb @@ -81,4 +81,40 @@ to raise_error(Vagrant::Errors::CLIInvalidUsage) end end + + context "with architecture flag" do + let(:argv) { ["foo", "--architecture", "test-arch"] } + + it "should execute runner with box architecture set" do + expect(action_runner).to receive(:run) do |_, opts| + expect(opts[:box_architecture]).to eq("test-arch") + end + + subject.execute + end + end + + context "with all providers flag" do + let(:argv) { ["foo", "--all-providers"] } + + it "should execute runner with all providers enabled" do + expect(action_runner).to receive(:run) do |_, opts| + expect(opts[:box_remove_all_providers]).to be(true) + end + + subject.execute + end + end + + context "with all architectures flag" do + let(:argv) { ["foo", "--all-architectures"] } + + it "should execute runner with all architectures enabled" do + expect(action_runner).to receive(:run) do |_, opts| + expect(opts[:box_remove_all_architectures]).to be(true) + end + + subject.execute + end + end end diff --git a/test/unit/plugins/commands/box/command/update_test.rb b/test/unit/plugins/commands/box/command/update_test.rb index 2d9007ed7cb..76b4fb3fb51 100644 --- a/test/unit/plugins/commands/box/command/update_test.rb +++ b/test/unit/plugins/commands/box/command/update_test.rb @@ -37,18 +37,16 @@ describe "execute" do context "updating specific box" do let(:argv) { ["--box", "foo"] } - let(:scratch) { Dir.mktmpdir("vagrant-test-command-box-update-execute") } - let(:metadata_url) { Pathname.new(scratch).join("metadata.json") } - + let(:box_args) { ["foo", "1.0", :virtualbox] } + let(:box_opts) { {metadata_url: metadata_url.to_s} } before do metadata_url.open("w") do |f| f.write("") end - test_iso_env.box3( - "foo", "1.0", :virtualbox, metadata_url: metadata_url.to_s) + test_iso_env.box3(*box_args, **box_opts) end after do @@ -72,43 +70,43 @@ it "does the correct update if there is an update" do metadata_url.open("w") do |f| - f.write(<<-RAW) - { - "name": "foo", - "versions": [ - { - "version": "1.0" - }, - { - "version": "1.8", - "providers": [ + f.write( + { + name: "foo", + versions: [ { - "name": "virtualbox", - "url": "bar" - } - ] - }, - { - "version": "1.10", - "providers": [ + version: "1.0" + }, { - "name": "virtualbox", - "url": "bar" - } - ] - }, - { - "version": "1.11", - "providers": [ + version: "1.8", + providers: [ + { + name: "virtualbox", + url: "bar" + } + ] + }, + { + version: "1.10", + providers: [ + { + name: "virtualbox", + url: "bar" + } + ] + }, { - "name": "virtualbox", - "url": "bar" + version: "1.11", + providers: [ + { + name: "virtualbox", + url: "bar" + } + ] } - ] - } - ] - } - RAW + ] + }.to_json + ) end action_called = false @@ -147,25 +145,25 @@ it "updates the proper box" do metadata_url.open("w") do |f| - f.write(<<-RAW) - { - "name": "foo", - "versions": [ - { - "version": "1.0" - }, - { - "version": "1.1", - "providers": [ - { - "name": "vmware", - "url": "bar" - } - ] - } - ] - } - RAW + f.write( + { + name: "foo", + versions: [ + { + version: "1.0" + }, + { + version: "1.1", + providers: [ + { + name: "vmware", + url: "bar" + } + ] + } + ] + }.to_json + ) end test_iso_env.box3("foo", "1.0", :vmware) @@ -200,25 +198,25 @@ it "passes down download options" do metadata_url.open("w") do |f| - f.write(<<-RAW) - { - "name": "foo", - "versions": [ - { - "version": "1.0" - }, - { - "version": "1.1", - "providers": [ + f.write( + { + name: "foo", + versions: [ { - "name": "virtualbox", - "url": "bar" + version: "1.0" + }, + { + version: "1.1", + providers: [ + { + name: "virtualbox", + url: "bar" + } + ] } - ] - } - ] - } - RAW + ] + }.to_json + ) end action_called = false @@ -249,6 +247,174 @@ to raise_error(Vagrant::Errors::BoxNotFound) end end + + context "with architecture" do + let(:box_opts) { {metadata_url: metadata_url.to_s, architecture: "test-arch"} } + + it "doesn't update if they're up to date" do + called = false + allow(action_runner).to receive(:run) do |callable, opts| + if opts[:box_provider] + called = true + end + + opts + end + + subject.execute + + expect(called).to be(false) + end + + it "does the correct update if there is an update" do + metadata_url.open("w") do |f| + f.write( + { + name: "foo", + versions: [ + { + version: "1.0" + }, + { + version: "1.8", + providers: [ + { + name: "virtualbox", + url: "bar", + architecture: "test-arch", + default_architecture: true + } + ] + }, + { + version: "1.10", + providers: [ + { + name: "virtualbox", + url: "bar", + architecture: "test-arch", + default_architecture: true + } + ] + }, + { + version: "1.11", + providers: [ + { + name: "virtualbox", + url: "bar", + architecture: "test-arch", + default_architecture: true + } + ] + } + ] + }.to_json + ) + end + + action_called = false + allow(action_runner).to receive(:run) do |action, opts| + if opts[:box_provider] + action_called = true + expect(opts[:box_force]).to eq(nil) + expect(opts[:box_url]).to eq(metadata_url.to_s) + expect(opts[:box_provider]).to eq("virtualbox") + expect(opts[:box_version]).to eq("1.11") + expect(opts[:box_architecture]).to eq("test-arch") + expect(opts[:box_download_ca_path]).to be_nil + expect(opts[:box_download_ca_cert]).to be_nil + expect(opts[:box_download_client_cert]).to be_nil + expect(opts[:box_download_insecure]).to be_nil + end + + opts + end + + subject.execute + + expect(action_called).to be(true) + end + + it "raises an error if there are multiple providers" do + test_iso_env.box3("foo", "1.0", :vmware) + + expect(action_runner).to receive(:run).never + + expect { subject.execute }. + to raise_error(Vagrant::Errors::BoxUpdateMultiProvider) + end + + it "raises an error if there are multiple architectures" do + test_iso_env.box3("foo", "1.0", :virtualbox, architecture: "other-arch") + + expect(action_runner).to receive(:run).never + + expect { subject.execute }. + to raise_error(Vagrant::Errors::BoxUpdateMultiArchitecture) + end + + context "with multiple architectures and specifying the architecture" do + let(:argv) { ["--box", "foo", "--architecture", "other-arch"] } + + it "updates the proper box" do + metadata_url.open("w") do |f| + f.write( + { + name: "foo", + versions: [ + { + version: "1.0" + }, + { + version: "1.1", + providers: [ + { + name: "virtualbox", + url: "bar", + architecture: "test-arch", + }, + { + name: "virtualbox", + url: "bar", + architecture: "other-arch", + } + ] + } + ] + }.to_json + ) + end + + test_iso_env.box3("foo", "1.0", :virtualbox, architecture: "other-arch") + + action_called = false + allow(action_runner).to receive(:run) do |action, opts| + if opts[:box_provider] + action_called = true + expect(opts[:box_url]).to eq(metadata_url.to_s) + expect(opts[:box_provider]).to eq("virtualbox") + expect(opts[:box_version]).to eq("1.1") + expect(opts[:box_architecture]).to eq("other-arch") + end + + opts + end + + subject.execute + + expect(action_called).to be(true) + end + + it "raises an error if that provider doesn't exist" do + expect(action_runner).to receive(:run).never + + expect { subject.execute }. + to raise_error(Vagrant::Errors::BoxNotFoundWithProviderArchitecture) + end + end + + end end context "updating environment machines" do @@ -286,25 +452,27 @@ context "boxes have an update" do let(:md) { - md = Vagrant::BoxMetadata.new(StringIO.new(<<-RAW)) - { - "name": "foo", - "versions": [ + Vagrant::BoxMetadata.new( + StringIO.new( { - "version": "1.0" - }, - { - "version": "1.1", - "providers": [ - { - "name": "virtualbox", - "url": "bar" - } + name: "foo", + versions: [ + { + version: "1.0" + }, + { + version: "1.1", + providers: [ + { + name: "virtualbox", + url: "bar" + } + ] + } ] - } - ] - } - RAW + }.to_json + ) + ) } before { allow(machine).to receive(:box).and_return(box) } diff --git a/test/unit/plugins/commands/cloud/box/show_test.rb b/test/unit/plugins/commands/cloud/box/show_test.rb index 398f70b990d..460c77fe6fa 100644 --- a/test/unit/plugins/commands/cloud/box/show_test.rb +++ b/test/unit/plugins/commands/cloud/box/show_test.rb @@ -40,7 +40,7 @@ end it "should display the box results" do - expect(subject).to receive(:format_box_results).with(box, env) + expect(subject).to receive(:format_box_results).with(box, env, {}) subject.show_box(org_name, box_name, access_token, options) end @@ -62,7 +62,7 @@ end it "should print the version details" do - expect(subject).to receive(:format_box_results).with(box_version, env) + expect(subject).to receive(:format_box_results).with(box_version, env, {}) subject.show_box(org_name, box_name, access_token, options) end @@ -144,6 +144,34 @@ subject.execute end end + + context "with architectures flag set" do + let(:arch_option) { "test-arch" } + + before { argv.push("--architectures").push(arch_option) } + + it "shouild show box with architectures option set" do + expect(subject).to receive(:show_box) do |*_, opts| + expect(opts[:architectures]).to eq([arch_option]) + end + + subject.execute + end + end + + context "with providers flag set" do + let(:provider_option) { "test-provider" } + + before { argv.push("--providers").push(provider_option) } + + it "shilud show box with providers option set" do + expect(subject).to receive(:show_box) do |*_, opts| + expect(opts[:providers]).to eq([provider_option]) + end + + subject.execute + end + end end end end diff --git a/test/unit/plugins/commands/cloud/provider/create_test.rb b/test/unit/plugins/commands/cloud/provider/create_test.rb index 09ca15699b6..64f2c57c3b0 100644 --- a/test/unit/plugins/commands/cloud/provider/create_test.rb +++ b/test/unit/plugins/commands/cloud/provider/create_test.rb @@ -43,7 +43,7 @@ subject { described_class.new(argv, env) } it "should add a new provider to the box version" do - expect(version).to receive(:add_provider).with(provider_name) + expect(version).to receive(:add_provider).with(provider_name, nil) subject.create_provider(org_name, box_name, box_version, provider_name, provider_url, access_token, options) end @@ -166,6 +166,59 @@ subject.execute end end + + it "should include detected host architecture by default" do + host_arch = double("host-arch") + expect(Vagrant::Util::Platform).to receive(:architecture).and_return(host_arch) + + expect(subject).to receive(:create_provider) do |*_, opts| + expect(opts[:architecture]).to eq(host_arch) + end + + subject.execute + end + + context "with architecture flag" do + let(:arch_option) { "test-arch" } + + before { argv.push("--architecture").push(arch_option) } + + it "should include the architecture option" do + expect(subject).to receive(:create_provider) do |*args, opts| + expect(opts[:architecture]).to eq(arch_option) + end + + subject.execute + end + end + + context "with default architecture flag" do + before { argv.push(default_arch_flag) } + + context "when flag is enabled" do + let(:default_arch_flag) { "--default-architecture" } + + it "should include default architecture with true value" do + expect(subject).to receive(:create_provider) do |*args, opts| + expect(opts[:default_architecture]).to be(true) + end + + subject.execute + end + end + + context "when flag is disabled" do + let(:default_arch_flag) { "--no-default-architecture" } + + it "should include default architecture with false value" do + expect(subject).to receive(:create_provider) do |*args, opts| + expect(opts[:default_architecture]).to be(false) + end + + subject.execute + end + end + end end end end diff --git a/test/unit/plugins/commands/cloud/provider/delete_test.rb b/test/unit/plugins/commands/cloud/provider/delete_test.rb index a9710e11ba5..2a27495f7ba 100644 --- a/test/unit/plugins/commands/cloud/provider/delete_test.rb +++ b/test/unit/plugins/commands/cloud/provider/delete_test.rb @@ -16,7 +16,8 @@ let(:organization) { double("organization") } let(:box) { double("box", versions: [version]) } let(:version) { double("version", version: box_version, providers: [provider]) } - let(:provider) { double("provider", name: box_version_provider) } + let(:provider) { double("provider", name: box_version_provider, architecture: architecture) } + let(:architecture) { double("test-architecture") } describe "#delete_provider" do let(:options) { {} } @@ -30,8 +31,14 @@ with(custom_server: anything, access_token: access_token). and_return(account) allow(subject).to receive(:with_provider). - with(account: account, org: org_name, box: box_name, version: box_version, provider: box_version_provider). - and_yield(provider) + with( + account: account, + org: org_name, + box: box_name, + version: box_version, + provider: box_version_provider, + architecture: architecture + ).and_yield(provider) allow(provider).to receive(:delete) end @@ -39,11 +46,11 @@ it "should delete the provider" do expect(provider).to receive(:delete) - subject.delete_provider(org_name, box_name, box_version, box_version_provider, access_token, options) + subject.delete_provider(org_name, box_name, box_version, box_version_provider, architecture, account, options) end it "should return zero on success" do - r = subject.delete_provider(org_name, box_name, box_version, box_version_provider, access_token, options) + r = subject.delete_provider(org_name, box_name, box_version, box_version_provider, architecture, account, options) expect(r).to eq(0) end @@ -53,7 +60,7 @@ end it "should return non-zero" do - r = subject.delete_provider(org_name, box_name, box_version, box_version_provider, access_token, options) + r = subject.delete_provider(org_name, box_name, box_version, box_version_provider, architecture, account, options) expect(r).to be_a(Integer) expect(r).not_to eq(0) end @@ -75,11 +82,16 @@ let(:client) { double("client", token: access_token) } before do + allow(VagrantCloud::Account).to receive(:new).and_return(account) + allow(account).to receive(:organization).with(name: org_name). + and_return(organization) allow(iso_env).to receive(:action_runner).and_return(action_runner) allow(subject).to receive(:client_login). and_return(client) allow(iso_env.ui).to receive(:ask). and_return("y") + allow(subject).to receive(:select_provider_architecture). + and_return(architecture) allow(subject).to receive(:delete_provider) end @@ -115,7 +127,7 @@ it "should delete the provider" do expect(subject).to receive(:delete_provider). - with(org_name, box_name, version_arg, provider_arg, access_token, anything) + with(org_name, box_name, version_arg, provider_arg, architecture, account, anything) subject.execute end @@ -124,6 +136,43 @@ subject.execute end + context "with architecture argument" do + let(:architecture_argument) { "test-arch" } + + before { argv << architecture_argument } + + it "should delete the provider" do + expect(subject).to receive(:delete_provider). + with(org_name, box_name, version_arg, provider_arg, architecture_argument, account, anything) + subject.execute + end + + it "should not attempt to select the architecture" do + expect(subject).not_to receive(:select_provider_architecture) + subject.execute + end + end + + context "with multiple provider architectures" do + let(:box_version) { double("box-version", providers: providers) } + let(:providers) { + [ + double("dummy-provider", architecture: "amd64"), + double("dummy-provider", architecture: "arm64") + ] + } + + before do + expect(subject).to receive(:select_provider_architecture).and_call_original + expect(subject).to receive(:with_version).and_yield(box_version) + end + + it "should prompt for architecture selection" do + expect(iso_env.ui).to receive(:ask).and_return("amd64") + subject.execute + end + end + context "with force flag" do before { argv << "--force" } diff --git a/test/unit/plugins/commands/cloud/provider/update_test.rb b/test/unit/plugins/commands/cloud/provider/update_test.rb index 343fc02c438..3e5b2de0a6f 100644 --- a/test/unit/plugins/commands/cloud/provider/update_test.rb +++ b/test/unit/plugins/commands/cloud/provider/update_test.rb @@ -12,6 +12,7 @@ let(:box_name) { "my-box" } let(:box_version) { "1.0.0" } let(:box_version_provider) { "my-provider" } + let(:box_architecture) { "box-architecture" } let(:account) { double("account") } let(:organization) { double("organization") } let(:box) { double("box", versions: [version]) } @@ -31,7 +32,14 @@ with(custom_server: anything, access_token: access_token). and_return(account) allow(subject).to receive(:with_provider). - with(account: account, org: org_name, box: box_name, version: box_version, provider: box_version_provider). + with( + account: account, + org: org_name, + box: box_name, + version: box_version, + provider: box_version_provider, + architecture: box_architecture + ). and_yield(provider) allow(provider).to receive(:save) allow(subject).to receive(:format_box_results) @@ -41,24 +49,60 @@ it "should update the provider" do expect(provider).to receive(:save) - subject.update_provider(org_name, box_name, box_version, box_version_provider, provider_url, access_token, options) + subject.update_provider( + org_name, + box_name, + box_version, + box_version_provider, + box_architecture, + provider_url, + access_token, + options + ) end it "should return 0 on success" do - result = subject.update_provider(org_name, box_name, box_version, box_version_provider, provider_url, access_token, options) + result = subject.update_provider( + org_name, + box_name, + box_version, + box_version_provider, + box_architecture, + provider_url, + access_token, + options + ) expect(result).to eq(0) end it "should return non-zero result on error" do expect(provider).to receive(:save).and_raise(VagrantCloud::Error) - result = subject.update_provider(org_name, box_name, box_version, box_version_provider, provider_url, access_token, options) + result = subject.update_provider( + org_name, + box_name, + box_version, + box_version_provider, + box_architecture, + provider_url, + access_token, + options + ) expect(result).not_to eq(0) expect(result).to be_a(Integer) end it "should not update the URL when unset" do expect(provider).not_to receive(:url=) - subject.update_provider(org_name, box_name, box_version, box_version_provider, provider_url, access_token, options) + subject.update_provider( + org_name, + box_name, + box_version, + box_version_provider, + box_architecture, + provider_url, + access_token, + options + ) end context "when URL is set" do @@ -66,19 +110,78 @@ it "should update the URL" do expect(provider).to receive(:url=).with(provider_url) - subject.update_provider(org_name, box_name, box_version, box_version_provider, provider_url, access_token, options) + subject.update_provider( + org_name, + box_name, + box_version, + box_version_provider, + box_architecture, + provider_url, + access_token, + options + ) end end context "with options set" do let(:checksum) { double("checksum") } let(:checksum_type) { double("checksum_type") } - let(:options) { {checksum: checksum, checksum_type: checksum_type} } + let(:options) { {} } + + after do + subject.update_provider( + org_name, + box_name, + box_version, + box_version_provider, + box_architecture, + provider_url, + access_token, + options + ) + end + + it "should not modify option controlled values when unset" do + expect(provider).not_to receive(:checksum=) + expect(provider).not_to receive(:checksum_type=) + expect(provider).not_to receive(:architecture=) + expect(provider).not_to receive(:default_architecture=) + end + + context "with checksum options set" do + let(:options) { {checksum: checksum, checksum_type: checksum_type} } - it "should set checksum options before saving" do - expect(provider).to receive(:checksum=).with(checksum) - expect(provider).to receive(:checksum_type=).with(checksum_type) - subject.update_provider(org_name, box_name, box_version, box_version_provider, provider_url, access_token, options) + it "should set checksum options before saving" do + expect(provider).to receive(:checksum=).with(checksum) + expect(provider).to receive(:checksum_type=).with(checksum_type) + end + end + + context "with architecture option set" do + let(:architecture) { double("architecture") } + let(:options) { {architecture: architecture} } + + it "should set architecture before saving" do + expect(provider).to receive(:architecture=).with(architecture) + end + end + + context "with default architecture option set" do + context "with true value" do + let(:options) { {default_architecture: true} } + + it "should set default architecture to true" do + expect(provider).to receive(:default_architecture=).with(true) + end + end + + context "with false value" do + let(:options) { {default_architecture: false} } + + it "should set default architecture to false" do + expect(provider).to receive(:default_architecture=).with(false) + end + end end end end @@ -134,37 +237,126 @@ before { argv << version_arg } - it "should create the provider" do - expect(subject).to receive(:update_provider).with(org_name, box_name, version_arg, provider_arg, any_args) - subject.execute + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) end - it "should not provide URL value" do - expect(subject).to receive(:update_provider).with(org_name, box_name, version_arg, provider_arg, nil, any_args) - subject.execute - end + context "with architecture argument" do + let(:architecture_arg) { "box-architecture" } + + before { argv << architecture_arg } + + it "should create the provider" do + expect(subject). + to receive(:update_provider). + with( + org_name, + box_name, + version_arg, + provider_arg, + architecture_arg, + any_args + ) + subject.execute + end - context "with URL argument" do - let(:url_arg) { "provider-url" } + it "should not provide URL value" do + expect(subject). + to receive(:update_provider). + with( + org_name, + box_name, + version_arg, + provider_arg, + architecture_arg, + nil, + any_args + ) + subject.execute + end - before { argv << url_arg } + context "with URL argument" do + let(:url_arg) { "provider-url" } + + before { argv << url_arg } + + it "should provide the URL value" do + expect(subject). + to receive(:update_provider). + with( + org_name, + box_name, + version_arg, + provider_arg, + architecture_arg, + url_arg, + any_args + ) + subject.execute + end + end - it "should provide the URL value" do - expect(subject).to receive(:update_provider).with(org_name, box_name, version_arg, provider_arg, url_arg, any_args) - subject.execute + context "with checksum and checksum type flags" do + let(:checksum_arg) { "checksum" } + let(:checksum_type_arg) { "checksum_type" } + + before { argv.push("--checksum").push(checksum_arg).push("--checksum-type").push(checksum_type_arg) } + + it "should include the checksum options" do + expect(subject). + to receive(:update_provider). + with( + org_name, + box_name, + version_arg, + provider_arg, + architecture_arg, + any_args, + hash_including( + checksum: checksum_arg, + checksum_type: checksum_type_arg + ) + ) + subject.execute + end end - end - context "with checksum and checksum type flags" do - let(:checksum_arg) { "checksum" } - let(:checksum_type_arg) { "checksum_type" } + context "with architecture flag" do + let(:architecture_flag) { "test-arch" } - before { argv.push("--checksum").push(checksum_arg).push("--checksum-type").push(checksum_type_arg) } + before { argv.push("--architecture").push(architecture_flag) } - it "should include the checksum options" do - expect(subject).to receive(:update_provider). - with(org_name, box_name, version_arg, provider_arg, any_args, hash_including(checksum: checksum_arg, checksum_type: checksum_type_arg)) - subject.execute + it "should include the architecture flag" do + expect(subject).to receive(:update_provider) do |*_, opts| + expect(opts[:architecture]).to eq(architecture_flag) + end + subject.execute + end + end + + context "with default architecture flag" do + context "enabled" do + before { argv.push("--default-architecture") } + + it "should include default architecture set to true" do + expect(subject).to receive(:update_provider) do |*_, opts| + expect(opts[:default_architecture]).to be(true) + end + subject.execute + end + end + + context "disabled" do + before { argv.push("--no-default-architecture") } + + it "should include default architecture set to false" do + expect(subject).to receive(:update_provider) do |*_, opts| + expect(opts[:default_architecture]).to be(false) + end + subject.execute + end + end end end end diff --git a/test/unit/plugins/commands/cloud/publish_test.rb b/test/unit/plugins/commands/cloud/publish_test.rb index c55ffd8b35f..38938fe0f11 100644 --- a/test/unit/plugins/commands/cloud/publish_test.rb +++ b/test/unit/plugins/commands/cloud/publish_test.rb @@ -20,16 +20,20 @@ let(:ui) { Vagrant::UI::Silent.new } let(:upload_url) { double("upload_url") } let(:access_token) { double("access_token") } + let(:default_architecture) { double("default-architecture") } subject { described_class.new(argv, iso_env) } before do + allow(Vagrant::Util::Platform).to receive(:architecture). + and_return(default_architecture) allow(iso_env).to receive(:ui).and_return(ui) allow(File).to receive(:stat).with(box). and_return(double("box_stat", size: box_size)) allow(VagrantCloud::Account).to receive(:new). with(custom_server: anything, access_token: anything). and_return(account) + allow(provider).to receive(:architecture=).with(default_architecture) end describe "#upload_box_file" do @@ -148,21 +152,69 @@ let(:options) { {} } it "should not modify the provider" do + expect(provider).not_to receive(:url=) + expect(provider).not_to receive(:checksum=) + expect(provider).not_to receive(:checksum_type=) + expect(provider).not_to receive(:architecture=) + expect(provider).not_to receive(:default_architecture=) + subject.set_provider_info(provider, options) end end - context "with options set" do - let(:options) { {url: url, checksum: checksum, checksum_type: checksum_type} } + context "with options" do + let(:options) { {} } let(:url) { double("url") } let(:checksum) { double("checksum") } let(:checksum_type) { double("checksum_type") } + let(:architecture) { double("architecture") } - it "should set info on provider" do - expect(provider).to receive(:url=).with(url) - expect(provider).to receive(:checksum=).with(checksum) - expect(provider).to receive(:checksum_type=).with(checksum_type) - subject.set_provider_info(provider, options) + after { subject.set_provider_info(provider, options) } + + context "with url set" do + before { options[:url] = url } + + it "should set url on provider" do + expect(provider).to receive(:url=).with(url) + end + end + + context "with checksum set" do + before do + options[:checksum] = checksum + options[:checksum_type] = checksum_type + end + + it "should set checksum on provider" do + expect(provider).to receive(:checksum=).with(checksum) + expect(provider).to receive(:checksum_type=).with(checksum_type) + end + end + + context "with architecture set" do + before { options[:architecture] = architecture } + + it "should set architecture on provider" do + expect(provider).to receive(:architecture=).with(architecture) + end + end + + context "with default architecture set" do + context "with true value" do + before { options[:default_architecture] = true } + + it "should set default architecture to true" do + expect(provider).to receive(:default_architecture=).with(true) + end + end + + context "with false value" do + before { options[:default_architecture] = false } + + it "should set default architecture to false" do + expect(provider).to receive(:default_architecture=).with(false) + end + end end end end diff --git a/test/unit/plugins/commands/cloud/search_test.rb b/test/unit/plugins/commands/cloud/search_test.rb index 6fca8f761b8..497b156cfc9 100644 --- a/test/unit/plugins/commands/cloud/search_test.rb +++ b/test/unit/plugins/commands/cloud/search_test.rb @@ -45,6 +45,7 @@ end context "with valid options" do + let(:architecture) { double("architecture") } let(:provider) { double("provider") } let(:sort) { double("sort") } let(:order) { double("order") } @@ -52,6 +53,7 @@ let(:page) { double("page") } let(:options) { { + architecture: architecture, provider: provider, sort: sort, order: order, diff --git a/test/unit/plugins/kernel_v2/config/vm_test.rb b/test/unit/plugins/kernel_v2/config/vm_test.rb index b23e53f63a9..265d07a2aad 100644 --- a/test/unit/plugins/kernel_v2/config/vm_test.rb +++ b/test/unit/plugins/kernel_v2/config/vm_test.rb @@ -116,6 +116,34 @@ def find_network(name) end end + describe "#box_architecture" do + it "is not required" do + subject.box_architecture = nil + subject.finalize! + assert_valid + end + + it "is :auto by default" do + subject.finalize! + assert_valid + expect(subject.box_architecture).to eq(:auto) + end + + it "can be set to custom value" do + subject.box_architecture = "test-arch" + subject.finalize! + assert_valid + expect(subject.box_architecture).to eq("test-arch") + end + + it "is converted to string" do + subject.box_architecture = :test_arch + subject.finalize! + assert_valid + expect(subject.box_architecture).to eq("test_arch") + end + end + context "#box_check_update" do it "defaults to true" do with_temp_env("VAGRANT_BOX_UPDATE_CHECK_DISABLE" => "") do diff --git a/test/unit/plugins/providers/virtualbox/driver/version_7_0_test.rb b/test/unit/plugins/providers/virtualbox/driver/version_7_0_test.rb index ea7d8a1f14c..5ecadbbe48d 100644 --- a/test/unit/plugins/providers/virtualbox/driver/version_7_0_test.rb +++ b/test/unit/plugins/providers/virtualbox/driver/version_7_0_test.rb @@ -810,24 +810,6 @@ UpperIP: 192.168.22.0 VBoxNetworkName: hostonly-vagrantnet-vbox2) -VBOX_HOSTONLYNETS=%(Name: vagrantnet-vbox1 -GUID: 10000000-0000-0000-0000-000000000000 - -State: Enabled -NetworkMask: 255.255.255.0 -LowerIP: 192.168.61.0 -UpperIP: 192.168.61.0 -VBoxNetworkName: hostonly-vagrantnet-vbox1 - -Name: vagrantnet-vbox2 -GUID: 20000000-0000-0000-0000-000000000000 - -State: Enabled -NetworkMask: 255.255.255.0 -LowerIP: 192.168.22.0 -UpperIP: 192.168.22.0 -VBoxNetworkName: hostonly-vagrantnet-vbox2) - VBOX_RANGE_HOSTONLYNETS=%(Name: vagrantnet-vbox1 GUID: 10000000-0000-0000-0000-000000000000 diff --git a/test/unit/support/isolated_environment.rb b/test/unit/support/isolated_environment.rb index da31e553ceb..b1ecd47145a 100644 --- a/test/unit/support/isolated_environment.rb +++ b/test/unit/support/isolated_environment.rb @@ -100,8 +100,12 @@ def box2(name, provider, options=nil) # @param [String] provider # @return [Pathname] def box3(name, version, provider, **opts) + args = [name, version] + args << opts[:architecture].to_s if opts[:architecture] + args << provider.to_s + # Create the directory for the box - box_dir = boxes_dir.join(name, version, provider.to_s) + box_dir = boxes_dir.join(*args) box_dir.mkpath # Create the metadata.json for it diff --git a/test/unit/vagrant/action/builtin/box_add_test.rb b/test/unit/vagrant/action/builtin/box_add_test.rb index 14e6ed587c2..704e7626ea4 100644 --- a/test/unit/vagrant/action/builtin/box_add_test.rb +++ b/test/unit/vagrant/action/builtin/box_add_test.rb @@ -210,7 +210,7 @@ def with_web_server(path, **opts) env[:box_provider] = "virtualbox" expect(box_collection).to receive(:find).with( - "foo", ["virtualbox"], "0").and_return(box) + "foo", ["virtualbox"], "0", nil).and_return(box) expect(box_collection).to receive(:add).never expect(app).to receive(:call).never @@ -557,6 +557,78 @@ def with_web_server(path, **opts) FileUtils.rm_rf(td) end + it "adds from API endpoint when available" do + box_path = iso_env.box2_file(:virtualbox) + td = Pathname.new(Dir.mktmpdir("vagrant-test-box-api-endpoint")) + tf = td.join("mitchellh", "precise64.json") + tf.dirname.mkpath + tf.open("w") do |f| + f.write(<<-RAW) + { + "name": "mitchellh/precise64", + "versions": [ + { + "version": "0.5" + }, + { + "version": "0.7", + "providers": [ + { + "name": "virtualbox", + "url": "#{box_path}" + } + ] + } + ] + } + RAW + end + apif = td.join("api", "v2", "vagrant", "mitchellh", "precise64.json") + apif.dirname.mkpath + apif.open("w") do |f| + f.write(<<-RAW) + { + "name": "mitchellh/precise64", + "versions": [ + { + "version": "0.5" + }, + { + "version": "0.8", + "providers": [ + { + "name": "virtualbox", + "url": "#{box_path}" + } + ] + } + ] + } + RAW + end + + with_web_server(tf.dirname) do |port| + url = "http://127.0.0.1:#{port}" + env[:box_url] = "mitchellh/precise64.json" + env[:box_server_url] = url + + expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| + expect(name).to eq("mitchellh/precise64") + expect(version).to eq("0.8") + expect(checksum(path)).to eq(checksum(box_path)) + expect(opts[:metadata_url]).to eq( + "#{url}/api/v2/vagrant/#{env[:box_url]}") + true + }.and_return(box) + + expect(app).to receive(:call).with(env) + + subject.call(env) + end + + FileUtils.rm_rf(td) + end + it "add from shorthand path with configured server url" do box_path = iso_env.box2_file(:virtualbox) td = Pathname.new(Dir.mktmpdir("vagrant-test-box-add-server-url")) @@ -903,6 +975,305 @@ def with_web_server(path, **opts) subject.call(env) end + context "with box architecture configured" do + before do + allow(Vagrant::Util::Platform).to receive(:architecture).and_return("test-arch") + end + + context "set to :auto" do + before do + env[:box_architecture] = :auto + end + + it "adds the latest version of a box with only one provider and matching architecture" do + box_path = iso_env.box2_file(:virtualbox) + tf = Tempfile.new(["vagrant-box-latest-version", ".json"]).tap do |f| + f.write( + { + name: "foo/bar", + versions: [ + { + version: "0.5", + }, + { + version: "0.7", + providers: [ + { + name: "virtualbox", + url: "/dev/null/invalid.path", + architecture: "amd64", + default_architecture: true, + }, + { + name: "virtualbox", + url: "#{box_path}", + architecture: "test-arch", + default_architecture: false, + } + ] + } + ] + }.to_json + ) + f.close + end + + env[:box_url] = tf.path + expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| + expect(checksum(path)).to eq(checksum(box_path)) + expect(name).to eq("foo/bar") + expect(version).to eq("0.7") + expect(opts[:metadata_url]).to eq("file://#{tf.path}") + expect(opts[:architecture]).to eq("test-arch") + true + }.and_return(box) + + expect(app).to receive(:call).with(env) + + subject.call(env) + end + + it "adds the latest version of a box with only one provider and unknown architecture set as default" do + box_path = iso_env.box2_file(:virtualbox) + tf = Tempfile.new(["vagrant-box-latest-version", ".json"]).tap do |f| + f.write( + { + name: "foo/bar", + versions: [ + { + version: "0.5", + }, + { + version: "0.7", + providers: [ + { + name: "virtualbox", + url: "#{box_path}", + architecture: "unknown", + default_architecture: true, + }, + { + name: "virtualbox", + url: "/dev/null/invalid.path", + architecture: "amd64", + default_architecture: false, + } + ] + } + ] + }.to_json + ) + f.close + end + + env[:box_url] = tf.path + expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| + expect(checksum(path)).to eq(checksum(box_path)) + expect(name).to eq("foo/bar") + expect(version).to eq("0.7") + expect(opts[:metadata_url]).to eq("file://#{tf.path}") + expect(opts[:architecture]).to be_nil + true + }.and_return(box) + + expect(app).to receive(:call).with(env) + + subject.call(env) + end + + it "errors adding latest version of a box with only one provider and no matching architecture" do + allow(Vagrant::Util::Platform).to receive(:architecture).and_return("test-arch") + + box_path = iso_env.box2_file(:virtualbox) + tf = Tempfile.new(["vagrant-box-latest-version", ".json"]).tap do |f| + f.write( + { + name: "foo/bar", + versions: [ + { + version: "0.5", + }, + { + version: "0.7", + providers: [ + { + name: "virtualbox", + url: "#{box_path}", + architecture: "amd64", + default_architecture: true, + }, + { + name: "virtualbox", + url: "/dev/null/invalid.path", + architecture: "arm64", + default_architecture: false, + } + ] + } + ] + }.to_json + ) + f.close + end + + env[:box_url] = tf.path + + expect { + subject.call(env) + }.to raise_error(Vagrant::Errors::BoxAddNoMatchingVersion) + end + end + + context "set to nil" do + before do + env[:box_architecture] = nil + end + + it "adds the latest version of a box with only one provider and default architecture" do + allow(Vagrant::Util::Platform).to receive(:architecture).and_return("default-arch") + box_path = iso_env.box2_file(:virtualbox) + tf = Tempfile.new(["vagrant-box-latest-version", ".json"]).tap do |f| + f.write( + { + name: "foo/bar", + versions: [ + { + version: "0.5", + }, + { + version: "0.7", + providers: [ + { + name: "virtualbox", + url: "#{box_path}", + architecture: "default-arch", + default_architecture: true, + }, + { + name: "virtualbox", + url: "/dev/null/invalid.path", + architecture: "test-arch", + default_architecture: false, + } + ] + } + ] + }.to_json + ) + f.close + end + + env[:box_url] = tf.path + expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| + expect(checksum(path)).to eq(checksum(box_path)) + expect(name).to eq("foo/bar") + expect(version).to eq("0.7") + expect(opts[:metadata_url]).to eq("file://#{tf.path}") + expect(opts[:architecture]).to eq("default-arch") + true + }.and_return(box) + + expect(app).to receive(:call).with(env) + + subject.call(env) + end + end + + context "set to explicit value" do + before do + env[:box_architecture] = "arm64" + end + + it "adds the latest version of a box with only one provider and matching architecture" do + box_path = iso_env.box2_file(:virtualbox) + tf = Tempfile.new(["vagrant-box-latest-version", ".json"]).tap do |f| + f.write( + { + name: "foo/bar", + versions: [ + { + version: "0.5", + }, + { + version: "0.7", + providers: [ + { + name: "virtualbox", + url: "#{box_path}", + architecture: "arm64", + default_architecture: false, + }, + { + name: "virtualbox", + url: "/dev/null/invalid.path", + architecture: "test-arch", + default_architecture: true, + } + ] + } + ] + }.to_json + ) + f.close + end + + env[:box_url] = tf.path + expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| + expect(checksum(path)).to eq(checksum(box_path)) + expect(name).to eq("foo/bar") + expect(version).to eq("0.7") + expect(opts[:metadata_url]).to eq("file://#{tf.path}") + expect(opts[:architecture]).to eq("arm64") + true + }.and_return(box) + + expect(app).to receive(:call).with(env) + + subject.call(env) + end + + it "errors adding the latest version of a box with only one provider and no matching architecture" do + box_path = iso_env.box2_file(:virtualbox) + tf = Tempfile.new(["vagrant-box-latest-version", ".json"]).tap do |f| + f.write( + { + name: "foo/bar", + versions: [ + { + version: "0.5", + }, + { + version: "0.7", + providers: [ + { + name: "virtualbox", + url: "#{box_path}", + architecture: "amd64", + default_architecture: true, + }, + { + name: "virtualbox", + url: "/dev/null/invalid.path", + architecture: "386", + default_architecture: false, + } + ] + } + ] + }.to_json + ) + f.close + end + + env[:box_url] = tf.path + + expect { + subject.call(env) + }.to raise_error(Vagrant::Errors::BoxAddNoMatchingVersion) + end + end + end + it "adds the latest version of a box with the specified provider" do box_path = iso_env.box2_file(:vmware) tf = Tempfile.new(["vagrant-box-specific-provider", ".json"]).tap do |f| @@ -1304,7 +1675,7 @@ def with_web_server(path, **opts) env[:box_url] = tf.path expect(box_collection).to receive(:find). - with("foo/bar", "virtualbox", "0.7").and_return(box) + with("foo/bar", "virtualbox", "0.7", nil).and_return(box) expect(box_collection).to receive(:add).never expect(app).to receive(:call).never diff --git a/test/unit/vagrant/action/builtin/box_remove_test.rb b/test/unit/vagrant/action/builtin/box_remove_test.rb index 3100ea60733..e8d0b0edc8e 100644 --- a/test/unit/vagrant/action/builtin/box_remove_test.rb +++ b/test/unit/vagrant/action/builtin/box_remove_test.rb @@ -17,7 +17,7 @@ subject { described_class.new(app, env) } let(:box_collection) { double("box_collection") } - let(:home_path) { "foo" } + let(:home_path) { double("home-path") } let(:machine_index) { [] } let(:iso_env) { isolated_environment } @@ -27,12 +27,12 @@ end it "deletes the box if it is the only option" do - allow(box_collection).to receive(:all).and_return([["foo", "1.0", :virtualbox]]) + allow(box_collection).to receive(:all).and_return([["foo", "1.0", :virtualbox, nil]]) env[:box_name] = "foo" expect(box_collection).to receive(:find).with( - "foo", :virtualbox, "1.0").and_return(box) + "foo", :virtualbox, "1.0", nil).and_return(box) expect(box_collection).to receive(:clean).with(box.name) .and_return(true) expect(box).to receive(:destroy!).once @@ -54,7 +54,7 @@ env[:box_provider] = "virtualbox" expect(box_collection).to receive(:find).with( - "foo", :virtualbox, "1.0").and_return(box) + "foo", :virtualbox, "1.0", nil).and_return(box) expect(box_collection).to receive(:clean).with(box.name) .and_return(false) expect(box).to receive(:destroy!).once @@ -76,7 +76,7 @@ env[:box_version] = "1.0" expect(box_collection).to receive(:find).with( - "foo", :virtualbox, "1.0").and_return(box) + "foo", :virtualbox, "1.0", nil).and_return(box) expect(box_collection).to receive(:clean).with(box.name) .and_return(false) expect(box).to receive(:destroy!).once @@ -87,6 +87,131 @@ expect(env[:box_removed]).to equal(box) end + context "with architecture" do + let(:architecture) { "test-arch" } + let(:box) do + box_dir = iso_env.box3("foo", "1.0", :virtualbox, architecture: architecture) + Vagrant::Box.new("foo", :virtualbox, "1.0", box_dir, architecture: architecture) + end + + it "deletes the box if it is the only option" do + allow(box_collection).to receive(:all).and_return([["foo", "1.0", :virtualbox, architecture]]) + + env[:box_name] = "foo" + + expect(box_collection).to receive(:find). + with("foo", :virtualbox, "1.0", architecture). + and_return(box) + expect(box_collection).to receive(:clean). + with(box.name). + and_return(true) + expect(box).to receive(:destroy!).once + expect(app).to receive(:call).with(env).once + + subject.call(env) + + expect(env[:box_removed]).to equal(box) + end + + it "deletes the box with the specified provider if given" do + allow(box_collection).to receive(:all) + .and_return([ + ["foo", "1.0", :virtualbox, architecture], + ["foo", "1.0", :vmware, architecture], + ]) + + env[:box_name] = "foo" + env[:box_provider] = "virtualbox" + + expect(box_collection).to receive(:find).with( + "foo", :virtualbox, "1.0", architecture).and_return(box) + expect(box_collection).to receive(:clean).with(box.name) + .and_return(false) + expect(box).to receive(:destroy!).once + expect(app).to receive(:call).with(env).once + + subject.call(env) + + expect(env[:box_removed]).to equal(box) + end + + it "deletes the box with the specified version if given" do + allow(box_collection).to receive(:all) + .and_return([ + ["foo", "1.0", :virtualbox, architecture], + ["foo", "1.1", :virtualbox, architecture], + ]) + + env[:box_name] = "foo" + env[:box_version] = "1.0" + + expect(box_collection).to receive(:find).with( + "foo", :virtualbox, "1.0", architecture).and_return(box) + expect(box_collection).to receive(:clean).with(box.name) + .and_return(false) + expect(box).to receive(:destroy!).once + expect(app).to receive(:call).with(env).once + + subject.call(env) + + expect(env[:box_removed]).to equal(box) + end + + it "deletes the box with the specificed version and architecture" do + allow(box_collection).to receive(:all) + .and_return([ + ["foo", "1.0", :virtualbox, architecture], + ["foo", "1.0", :virtualbox, "other-arch"], + ]) + + env[:box_name] = "foo" + env[:box_version] = "1.0" + env[:box_architecture] = architecture + + expect(box_collection).to receive(:find).with( + "foo", :virtualbox, "1.0", architecture).and_return(box) + expect(box_collection).to receive(:clean).with(box.name) + .and_return(false) + expect(box).to receive(:destroy!).once + expect(app).to receive(:call).with(env).once + + subject.call(env) + + expect(env[:box_removed]).to equal(box) + end + + it "errors when box with specified version does not included specified architecture" do + allow(box_collection).to receive(:all) + .and_return([ + ["foo", "1.0", :virtualbox, architecture], + ["foo", "1.0", :virtualbox, "other-arch"], + ]) + + env[:box_name] = "foo" + env[:box_version] = "1.0" + env[:box_architecture] = "unknown-arch" + + expect { + subject.call(env) + }.to raise_error(Vagrant::Errors::BoxRemoveArchitectureNotFound) + end + + it "errors when box with specified version has multiple architectures" do + allow(box_collection).to receive(:all) + .and_return([ + ["foo", "1.0", :virtualbox, architecture], + ["foo", "1.0", :virtualbox, "other-arch"], + ]) + + env[:box_name] = "foo" + env[:box_version] = "1.0" + + expect { + subject.call(env) + }.to raise_error(Vagrant::Errors::BoxRemoveMultiArchitecture) + end + end + context "checking if a box is in use" do def new_entry(name, provider, version, valid=true) Vagrant::MachineIndex::Entry.new.tap do |entry| @@ -117,7 +242,7 @@ def new_entry(name, provider, version, valid=true) it "does delete if the box is not in use" do expect(box_collection).to receive(:find).with( - "foo", :virtualbox, "1.0").and_return(box) + "foo", :virtualbox, "1.0", nil).and_return(box) expect(box).to receive(:destroy!).once expect(box_collection).to receive(:clean).with(box.name) .and_return(true) @@ -133,7 +258,7 @@ def new_entry(name, provider, version, valid=true) with(anything, env).and_return(result) expect(box_collection).to receive(:find).with( - "foo", :virtualbox, "1.0").and_return(box) + "foo", :virtualbox, "1.0", nil).and_return(box) expect(box_collection).to receive(:clean).with(box.name) .and_return(true) expect(box).to receive(:destroy!).once @@ -149,7 +274,7 @@ def new_entry(name, provider, version, valid=true) with(anything, env).and_return(result) expect(box_collection).to receive(:find).with( - "foo", :virtualbox, "1.0").and_return(box) + "foo", :virtualbox, "1.0", nil).and_return(box) expect(box).to receive(:destroy!).never subject.call(env) @@ -164,7 +289,7 @@ def new_entry(name, provider, version, valid=true) with(anything, env).and_return(result) expect(box_collection).to receive(:find).with( - "foo", :virtualbox, "1.0").and_return(box) + "foo", :virtualbox, "1.0", nil).and_return(box) expect(box).to receive(:destroy!).never subject.call(env) @@ -178,7 +303,7 @@ def new_entry(name, provider, version, valid=true) with(anything, env).and_return(result) expect(box_collection).to receive(:find).with( - "foo", :virtualbox, "1.0").and_return(box) + "foo", :virtualbox, "1.0", nil).and_return(box) expect(box_collection).to receive(:clean).with(box.name) .and_return(true) expect(box).to receive(:destroy!) diff --git a/test/unit/vagrant/box_collection_test.rb b/test/unit/vagrant/box_collection_test.rb index 2d2795f4372..16eb6b2f3c6 100644 --- a/test/unit/vagrant/box_collection_test.rb +++ b/test/unit/vagrant/box_collection_test.rb @@ -36,11 +36,11 @@ # Verify some output results = subject.all expect(results.length).to eq(5) - expect(results.include?(["foo", "1.0", :virtualbox])).to be - expect(results.include?(["foo", "1.0", :vmware])).to be - expect(results.include?(["bar", "0", :ec2])).to be - expect(results.include?(["foo/bar", "1.0", :virtualbox])).to be - expect(results.include?(["foo:colon", "1.0", :virtualbox])).to be + expect(results).to include(["foo", "1.0", :virtualbox, nil]) + expect(results).to include(["foo", "1.0", :vmware, nil]) + expect(results).to include(["bar", "0", :ec2, nil]) + expect(results).to include(["foo/bar", "1.0", :virtualbox, nil]) + expect(results).to include(["foo:colon", "1.0", :virtualbox, nil]) end it "should return the boxes and their providers even if box has wrong version" do @@ -57,11 +57,11 @@ # Verify some output results = subject.all expect(results.length).to eq(4) - expect(results.include?(["foo", "1.0", :virtualbox])).not_to be - expect(results.include?(["foo", "1.0", :vmware])).to be - expect(results.include?(["bar", "0", :ec2])).to be - expect(results.include?(["foo/bar", "1.0", :virtualbox])).to be - expect(results.include?(["foo:colon", "1.0", :virtualbox])).to be + expect(results).not_to include(["foo", "1.0", :virtualbox, nil]) + expect(results).to include(["foo", "1.0", :vmware, nil]) + expect(results).to include(["bar", "0", :ec2, nil]) + expect(results).to include(["foo/bar", "1.0", :virtualbox, nil]) + expect(results).to include(["foo:colon", "1.0", :virtualbox, nil]) end it 'does not raise an exception when a file appears in the boxes dir' do @@ -175,7 +175,7 @@ # Make sure the results still exist results = subject.all expect(results.length).to eq(1) - expect(results.include?(["foo", "1.0", :vmware])).to be + expect(results).to include(["foo", "1.0", :vmware, nil]) end it "doesn't remove the directory if a version exists" do @@ -195,7 +195,7 @@ # Make sure the results still exist results = subject.all expect(results.length).to eq(1) - expect(results.include?(["foo", "1.0", :virtualbox])).to be + expect(results).to include(["foo", "1.0", :virtualbox, nil]) end end diff --git a/test/unit/vagrant/box_metadata_test.rb b/test/unit/vagrant/box_metadata_test.rb index b149d2f3113..ce6c438a414 100644 --- a/test/unit/vagrant/box_metadata_test.rb +++ b/test/unit/vagrant/box_metadata_test.rb @@ -9,34 +9,32 @@ include_context "unit" let(:raw) do - <<-RAW - { - "name": "foo", - "description": "bar", - "versions": [ - { - "version": "1.0.0", - "providers": [ - { "name": "virtualbox" }, - { "name": "vmware" } - ] - }, - { - "version": "1.1.5", - "providers": [ - { "name": "virtualbox" } - ] - }, - { - "version": "1.1.0", - "providers": [ - { "name": "virtualbox" }, - { "name": "vmware" } - ] - } - ] - } - RAW + { + name: "foo", + description: "bar", + versions: [ + { + version: "1.0.0", + providers: [ + { name: "virtualbox" }, + { name: "vmware" } + ], + }, + { + version: "1.1.5", + providers: [ + { name: "virtualbox" } + ] + }, + { + version: "1.1.0", + providers: [ + { name: "virtualbox" }, + { name: "vmware" } + ] + } + ] + }.to_json end subject { described_class.new(raw) } @@ -53,9 +51,7 @@ context "with poorly formatted JSON" do let(:raw) { - <<-RAW - { "name": "foo", } - RAW + {name: "foo"}.to_json + "," } it "raises an exception" do @@ -66,15 +62,14 @@ context "with poorly formatted version" do let(:raw) { - <<-RAW - { "name": "foo", - "versions": [ + { + name: "foo", + versions: [ { - "version": "I AM NOT VALID" + version: "I AM NOT VALID" } ] - } - RAW + }.to_json } it "raises an exception" do @@ -124,6 +119,213 @@ ["1.0.0", "1.1.0"]) end end + + context "with architecture" do + let(:raw) do + { + name: "foo", + description: "bar", + versions: [ + { + version: "1.0.0", + providers: [ + { + name: "virtualbox", + default_architecture: true, + architecture: "amd64" + }, + { + name: "virtualbox", + default_architecture: false, + architecture: "arm64" + }, + { + name: "vmware", + default_architecture: true, + architecture: "arm64" + }, + { + name: "vmware", + default_architecture: false, + architecture: "amd64" + } + ], + }, + { + version: "1.1.5", + providers: [ + { + name: "virtualbox", + architecture: "amd64", + default_architecture: true, + } + ] + }, + { + version: "1.1.6", + providers: [ + { + name: "virtualbox", + architecture: "arm64", + default_architecture: true, + }, + ] + }, + { + version: "1.1.0", + providers: [ + { + name: "virtualbox", + architecture: "amd64", + default_architecture: true, + }, + { + name: "vmware", + architecture: "amd64", + default_architecture: true, + } + ] + }, + { + version: "2.0.0", + providers: [ + { + name: "vmware", + architecture: "arm64", + default_architecture: true, + } + ] + } + ] + }.to_json + end + + subject { described_class.new(raw) } + + before { allow(Vagrant::Util::Platform).to receive(:architecture).and_return("amd64") } + + describe "#version" do + it "matches an exact version" do + result = subject.version("1.0.0") + expect(result).to_not be_nil + expect(result).to be_kind_of(Vagrant::BoxMetadata::Version) + expect(result.version).to eq("1.0.0") + end + + it "matches a constraint with latest matching version" do + result = subject.version(">= 1.0") + expect(result).to_not be_nil + expect(result).to be_kind_of(Vagrant::BoxMetadata::Version) + expect(result.version).to eq("1.1.5") + end + + it "matches complex constraints" do + result = subject.version(">= 0.9, ~> 1.0.0") + expect(result).to_not be_nil + expect(result).to be_kind_of(Vagrant::BoxMetadata::Version) + expect(result.version).to eq("1.0.0") + end + + context "with provider filter" do + it "matches the constraint that has the given provider" do + result = subject.version(">= 0", provider: :vmware) + expect(result).to_not be_nil + expect(result).to be_kind_of(Vagrant::BoxMetadata::Version) + expect(result.version).to eq("1.1.0") + end + + it "matches the exact version that has the given provider" do + result = subject.version("1.0.0", provider: :virtualbox) + expect(result).to_not be_nil + expect(result).to be_kind_of(Vagrant::BoxMetadata::Version) + expect(result.version).to eq("1.0.0") + end + + it "does not match exact version that has given provider but not host architecture" do + result = subject.version("1.1.6", provider: :virtualbox) + expect(result).to be_nil + end + + context "with architecture filter" do + it "matches the exact version that has provider with host architecture when using :auto" do + result = subject.version("1.0.0", provider: :virtualbox, architecture: :auto) + expect(result).to_not be_nil + expect(result).to be_kind_of(Vagrant::BoxMetadata::Version) + expect(result.version).to eq("1.0.0") + end + + it "matches the exact version that has provider with defined host architecture" do + result = subject.version("1.0.0", provider: :virtualbox, architecture: "arm64") + expect(result).to_not be_nil + expect(result).to be_kind_of(Vagrant::BoxMetadata::Version) + expect(result.version).to eq("1.0.0") + end + + it "does not match the exact version that has provider with defined host architecture" do + result = subject.version("1.0.0", provider: :virtualbox, architecture: "ppc64") + expect(result).to be_nil + end + end + end + + context "with architecture filter" do + it "matches a constraint that has the detected host architecture" do + result = subject.version("> 0", architecture: :auto) + expect(result).to be_kind_of(Vagrant::BoxMetadata::Version) + expect(result.version).to eq("1.1.5") + end + + it "matches a constraint that has the provided architecture" do + result = subject.version("> 0", architecture: "arm64") + expect(result).to be_kind_of(Vagrant::BoxMetadata::Version) + expect(result.version).to eq("2.0.0") + end + + it "matches exact version that has the provided architecture" do + result = subject.version("1.0.0", architecture: "arm64") + expect(result).to be_kind_of(Vagrant::BoxMetadata::Version) + expect(result.version).to eq("1.0.0") + end + + it "does not match exact version that does not have provided architecture" do + result = subject.version("2.0.0", architecture: "amd64") + expect(result).to be_nil + end + end + end + + describe "#versions" do + it "returns the versions it contained" do + expect(subject.versions). + to eq(["1.0.0", "1.1.0", "1.1.5", "1.1.6", "2.0.0"]) + end + + context "with provider filter" do + it "filters versions" do + expect(subject.versions(provider: :vmware)). + to eq(["1.0.0", "1.1.0", "2.0.0"]) + end + end + + context "with architecture filter" do + it "filters versions" do + expect(subject.versions(architecture: "arm64")). + to eq(["1.0.0", "1.1.6", "2.0.0"]) + end + + it "returns none when no matching architecture available" do + expect(subject.versions(architecture: "other")). + to be_empty + end + + it "filters based on host architecture when :auto used" do + expect(subject.versions(architecture: :auto)). + to eq(subject.versions(architecture: "amd64")) + end + end + end + + end end describe Vagrant::BoxMetadata::Version do @@ -203,4 +405,28 @@ expect(subject.checksum_type).to be_nil end end + + describe "architecture" do + it "is set properly" do + raw["architecture"] = "test-arch" + + expect(subject.architecture).to eq("test-arch") + end + + it "is nil if not set" do + expect(subject.architecture).to be_nil + end + end + + describe "#architecture_support?" do + it "is false if architecture is not supported" do + expect(subject.architecture_support?).to be(false) + end + + it "is true if architecture is supported" do + raw["default_architecture"] = false + + expect(subject.architecture_support?).to be(true) + end + end end diff --git a/test/unit/vagrant/box_test.rb b/test/unit/vagrant/box_test.rb index 13be39d1281..941823a2b1a 100644 --- a/test/unit/vagrant/box_test.rb +++ b/test/unit/vagrant/box_test.rb @@ -19,6 +19,7 @@ let(:name) { "foo" } let(:provider) { :virtualbox } let(:version) { "1.0" } + let(:architecture) { "test-architecture" } let(:directory) { environment.box3("foo", "1.0", :virtualbox) } subject { described_class.new(name, provider, version, directory) } @@ -209,6 +210,154 @@ expect(result[1].version).to eq("1.1") expect(result[2].url).to eq("bar") end + + context "with architecture" do + subject do + described_class.new( + name, provider, version, directory, + architecture: architecture, + metadata_url: "foo" + ) + end + + it "raises an exception if no metadata_url is set" do + subject = described_class.new( + name, provider, version, directory, + architecture: architecture, + ) + + expect { subject.has_update?("> 0") }. + to raise_error(Vagrant::Errors::BoxUpdateNoMetadata) + end + + it "returns nil if there is no update" do + metadata = Vagrant::BoxMetadata.new( + { + name: "foo", + versions: [ { version: "1.0" } ] + }.to_json + ) + allow(subject).to receive(:load_metadata).and_return(metadata) + + expect(subject.has_update?).to be_nil + end + + it "returns the updated box info if there is an update available" do + metadata = Vagrant::BoxMetadata.new( + { + name: "foo", + versions: [ + {version: "1.0"}, + { + version: "1.1", + providers: [ + { + name: "virtualbox", + url: "bar", + architecture: architecture, + } + ] + } + ] + }.to_json + ) + + allow(subject).to receive(:load_metadata).and_return(metadata) + + result = subject.has_update? + expect(result).to_not be_nil + + expect(result[0]).to be_kind_of(Vagrant::BoxMetadata) + expect(result[1]).to be_kind_of(Vagrant::BoxMetadata::Version) + expect(result[2]).to be_kind_of(Vagrant::BoxMetadata::Provider) + + expect(result[0].name).to eq("foo") + expect(result[1].version).to eq("1.1") + expect(result[2].url).to eq("bar") + end + + it "returns nil if update does not support architecture" do + metadata = Vagrant::BoxMetadata.new( + { + name: "foo", + versions: [ + {version: "1.0"}, + { + version: "1.1", + providers: [ + { + name: "virtualbox", + url: "bar", + architecture: "other-architecture", + } + ] + } + ] + }.to_json + ) + + allow(subject).to receive(:load_metadata).and_return(metadata) + + result = subject.has_update? + expect(result).to be_nil + end + + it "returns the updated box info within constraints" do + metadata = Vagrant::BoxMetadata.new( + { + name: "foo", + versions: [ + { + version: "1.0", + }, + { + version: "1.1", + providers: [ + { + name: "virtualbox", + url: "bar", + architecture: architecture + }, + ] + }, + { + version: "1.2", + providers: [ + { + name: "virtualbox", + url: "bar", + architecture: "other-architecture", + }, + ] + }, + { + version: "1.4", + providers: [ + { + name: "virtualbox", + url: "bar", + architecture: architecture + } + ] + } + ] + }.to_json + ) + + allow(subject).to receive(:load_metadata).and_return(metadata) + + result = subject.has_update?(">= 1.1, < 1.4") + expect(result).to_not be_nil + + expect(result[0]).to be_kind_of(Vagrant::BoxMetadata) + expect(result[1]).to be_kind_of(Vagrant::BoxMetadata::Version) + expect(result[2]).to be_kind_of(Vagrant::BoxMetadata::Provider) + + expect(result[0].name).to eq("foo") + expect(result[1].version).to eq("1.1") + expect(result[2].url).to eq("bar") + end + end end context "#automatic_update_check_allowed?" do @@ -236,12 +385,13 @@ context "#in_use?" do let(:index) { [] } - def new_entry(name, provider, version) + def new_entry(name, provider, version, architecture=nil) Vagrant::MachineIndex::Entry.new.tap do |entry| entry.extra_data["box"] = { "name" => name, "provider" => provider, "version" => version, + "architecture" => architecture, } end end @@ -261,6 +411,27 @@ def new_entry(name, provider, version) expect(subject.in_use?(index)).to eq([matching]) end + + context "with architecture information" do + subject { described_class.new(name, provider, version, directory, architecture: architecture) } + + it "returns nil if the index has no matching entries" do + index << new_entry("foo", "bar", "1.0", "amd64") + index << new_entry("foo", "baz", "1.2", "arm64") + index << new_entry(name, provider.to_s, version, "random-arch") + + expect(subject).to_not be_in_use(index) + end + + it "returns matching entries if they exist" do + matching = new_entry(name, provider.to_s, version, architecture) + index << new_entry("foo", "bar", "1.0", "amd64") + index << matching + index << new_entry("foo", "baz", "1.2") + + expect(subject.in_use?(index)).to eq([matching]) + end + end end context "#load_metadata" do diff --git a/test/unit/vagrant/machine_test.rb b/test/unit/vagrant/machine_test.rb index 7158b034596..12ba865bad4 100644 --- a/test/unit/vagrant/machine_test.rb +++ b/test/unit/vagrant/machine_test.rb @@ -26,6 +26,7 @@ double("box").tap do |b| allow(b).to receive(:name).and_return("foo") allow(b).to receive(:provider).and_return(:dummy) + allow(b).to receive(:architecture) allow(b).to receive(:version).and_return("1.0") end end @@ -580,6 +581,7 @@ def detect?(machine) box = double("box") allow(box).to receive(:name).and_return("foo") allow(box).to receive(:provider).and_return(:bar) + allow(box).to receive(:architecture) allow(box).to receive(:version).and_return("1.2.3") subject.box = box @@ -599,6 +601,7 @@ def detect?(machine) expect(entry.extra_data["box"]).to eq({ "name" => box.name, "provider" => box.provider.to_s, + "architecture" => nil, "version" => box.version, }) env.machine_index.release(entry) diff --git a/test/unit/vagrant/util/platform_test.rb b/test/unit/vagrant/util/platform_test.rb index d06c38dbec9..acf01f6080c 100644 --- a/test/unit/vagrant/util/platform_test.rb +++ b/test/unit/vagrant/util/platform_test.rb @@ -11,6 +11,70 @@ after { described_class.reset! } subject { described_class } + describe "#architecture" do + let(:cpu_string) { "unknown" } + + before do + allow(RbConfig::CONFIG). + to receive(:[]).with("target_cpu"). + and_return(cpu_string) + end + + context "when cpu is x86_64" do + let(:cpu_string) { "x86_64" } + + it "should be mapped to amd64" do + expect(described_class.architecture).to eq("amd64") + end + end + + context "when cpu is i386" do + let(:cpu_string) { "i386" } + + it "should be mapped to 386" do + expect(described_class.architecture).to eq("386") + end + end + + context "when cpu is arm64" do + let(:cpu_string) { "arm64" } + + it "should be arm64" do + expect(described_class.architecture).to eq("arm64") + end + end + + context "when cpu is aarch64" do + let(:cpu_string) { "aarch64" } + + it "should be mapped to arm64" do + expect(described_class.architecture).to eq("arm64") + end + end + + context "when cpu is unmapped value" do + let(:cpu_string) { "custom-cpu" } + + it "should be returned as-is" do + expect(described_class.architecture).to eq(cpu_string) + end + end + + context "when environment variable override is set" do + let(:host_override) { "custom-host-override" } + + before do + allow(ENV).to receive(:[]). + with("VAGRANT_HOST_ARCHITECTURE"). + and_return(host_override) + end + + it "should return the custom override" do + expect(subject.architecture).to eq(host_override) + end + end + end + describe "#cygwin_path" do let(:path) { "C:\\msys2\\home\\vagrant" } let(:updated_path) { "/home/vagrant" } diff --git a/test/unit/vagrant/vagrantfile_test.rb b/test/unit/vagrant/vagrantfile_test.rb index 4cf75d7055e..f2ec25db7ce 100644 --- a/test/unit/vagrant/vagrantfile_test.rb +++ b/test/unit/vagrant/vagrantfile_test.rb @@ -266,6 +266,44 @@ def self.usable?(raise_error=false) expect(box.version).to eq("1.3") end + it "configures with the custom box architecture" do + register_provider("foo") + configure do |config| + config.vm.box = "base" + config.vm.box_architecture = "custom-arch" + end + + results = subject.machine_config(:default, :foo, boxes) + config = results[:config] + expect(config.vm.box).to eq("base") + expect(config.vm.box_architecture).to eq("custom-arch") + end + + it "configures with the default box architecture" do + register_provider("foo") + configure do |config| + config.vm.box = "base" + end + + results = subject.machine_config(:default, :foo, boxes) + config = results[:config] + expect(config.vm.box).to eq("base") + expect(config.vm.box_architecture).to eq(:auto) + end + + it "configures box architecture to nil" do + register_provider("foo") + configure do |config| + config.vm.box = "base" + config.vm.box_architecture = nil + end + + results = subject.machine_config(:default, :foo, boxes) + config = results[:config] + expect(config.vm.box).to eq("base") + expect(config.vm.box_architecture).to be_nil + end + it "configures with box config of other supported formats" do register_provider("foo", nil, box_format: "bar") diff --git a/vagrant.gemspec b/vagrant.gemspec index 88cee701f83..80b54baccbc 100644 --- a/vagrant.gemspec +++ b/vagrant.gemspec @@ -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.1.0" 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" diff --git a/website/content/docs/boxes/box_repository.mdx b/website/content/docs/boxes/box_repository.mdx index bdd57662db9..3c3c2668216 100644 --- a/website/content/docs/boxes/box_repository.mdx +++ b/website/content/docs/boxes/box_repository.mdx @@ -50,7 +50,9 @@ It is a JSON document, structured in the following way: "name": "virtualbox", "url": "http://example.com/bionic64_010_virtualbox.box", "checksum_type": "sha1", - "checksum": "foo" + "checksum": "foo", + "architecture": "amd64", + "default_architecture": true } ] } @@ -59,7 +61,8 @@ It is a JSON document, structured in the following way: ``` As you can see, the JSON document can describe multiple versions of a box, -multiple providers, and can add/remove providers in different versions. +multiple providers, and can add/remove providers/architectures in different +versions. This JSON file can be passed directly to `vagrant box add` from the local filesystem using a file path or via a URL, and Vagrant will diff --git a/website/content/docs/vagrantfile/machine_settings.mdx b/website/content/docs/vagrantfile/machine_settings.mdx index e0bed5580fe..62e312b1f61 100644 --- a/website/content/docs/vagrantfile/machine_settings.mdx +++ b/website/content/docs/vagrantfile/machine_settings.mdx @@ -42,6 +42,13 @@ the name of the synced folder plugin. of an installed box or a shorthand name of a box in [HashiCorp's Vagrant Cloud](/vagrant/vagrant-cloud). +- `config.vm.box_architecture` (string) - The architecture of the box to be used. + Supported architecture values: "386", "amd64", "arm", "arm64", "ppc64le", "ppc64", + "mips64le", "mips64", "mipsle", "mips", and "s390x". The special value `:auto` will + detect the host architecture and fetch the appropriate box, if available. When the + value is set to `nil`, it will fetch the box flagged as the default architecture. + Defaults to `:auto`. + - `config.vm.box_check_update` (boolean) - If true, Vagrant will check for updates to the configured box on every `vagrant up`. If an update is found, Vagrant will tell the user. By default this is true. Updates will only be checked