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

Box collection has been modified to allow existing boxes already
downloaded and unpacked to still function as expected when architecture
information is not available.
2023-09-14 16:15:03 -07:00

207 lines
7.6 KiB
Ruby

# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1
require 'optparse'
require_relative 'download_mixins'
module VagrantPlugins
module CommandBox
module Command
class Update < Vagrant.plugin("2", :command)
include DownloadMixins
def execute
options = {}
download_options = {}
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant box update [options]"
o.separator ""
o.separator "Updates the box that is in use in the current Vagrant environment,"
o.separator "if there any updates available. This does not destroy/recreate the"
o.separator "machine, so you'll have to do that to see changes."
o.separator ""
o.separator "To update a specific box (not tied to a Vagrant environment), use the"
o.separator "--box flag."
o.separator ""
o.separator "Options:"
o.separator ""
o.on("--box BOX", String, "Update a specific box") do |b|
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
o.on("-f", "--force", "Overwrite an existing box if it exists") do |f|
options[:force] = f
end
build_download_options(o, download_options)
end
argv = parse_options(opts)
return if !argv
if options[:box]
update_specific(options[:box], options[:provider], options[:architecture], download_options, options[:force])
else
update_vms(argv, options[:provider], download_options, options[:force])
end
0
end
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 box_info.empty?
raise Vagrant::Errors::BoxNotFound, name: name.to_s
end
if !provider
if box_info.size > 1
raise Vagrant::Errors::BoxUpdateMultiProvider,
name: name.to_s,
providers: box_info.keys.map(&:to_s).sort.join(", ")
end
provider = box_info.keys.first
elsif !box_info[provider]
raise Vagrant::Errors::BoxNotFoundWithProvider,
name: name.to_s,
provider: provider.to_s,
providers: box_info.keys.map(&:to_s).sort.join(", ")
end
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
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)
machines = {}
with_target_vms(argv, provider: provider) do |machine|
if !machine.config.vm.box
machine.ui.output(I18n.t(
"vagrant.errors.box_update_no_name"))
next
end
if !machine.box
collection = Vagrant::BoxCollection.new(@env.boxes_path)
machine.box = collection.find(machine.config.vm.box, provider || machine.provider_name || @env.default_provider, "> 0")
if !machine.box
machine.ui.output(I18n.t(
"vagrant.errors.box_update_no_box",
name: machine.config.vm.box))
next
end
end
name = machine.box.name
provider = machine.box.provider
version = machine.config.vm.box_version || machine.box.version
machines["#{name}_#{provider}_#{version}"] = machine
end
machines.each do |_, machine|
box = machine.box
version = machine.config.vm.box_version
# Get download options from machine configuration if not specified
# on the command line.
download_options[:ca_cert] ||= machine.config.vm.box_download_ca_cert
download_options[:ca_path] ||= machine.config.vm.box_download_ca_path
download_options[:client_cert] ||= machine.config.vm.box_download_client_cert
if download_options[:insecure].nil?
download_options[:insecure] = machine.config.vm.box_download_insecure
end
begin
box_update(box, version, machine.ui, download_options, force)
rescue Vagrant::Errors::BoxUpdateNoMetadata => e
machine.ui.warn(e)
next
end
end
end
def box_update(box, version, ui, download_options, force)
ui.output(I18n.t("vagrant.box_update_checking", name: box.name))
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
ui.success(I18n.t(
"vagrant.box_up_to_date_single",
name: box.name, version: box.version))
return
end
ui.output(I18n.t(
"vagrant.box_updating",
name: update[0].name,
provider: update[2].name,
old: box.version,
new: update[1].version))
@env.action_runner.run(Vagrant::Action.action_box_add, {
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],
box_download_ca_cert: download_options[:ca_cert],
box_download_ca_path: download_options[:ca_path],
box_download_insecure: download_options[:insecure]
})
end
end
end
end
end