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.
207 lines
7.6 KiB
Ruby
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
|