# Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: BUSL-1.1 require "log4r" module Vagrant module Action module Builtin # This middleware will remove a box for a given provider. class BoxRemove def initialize(app, env) @app = app @logger = Log4r::Logger.new("vagrant::action::builtin::box_remove") end def call(env) box_name = env[:box_name] box_architecture = env[:box_architecture] if env[:box_architecture] box_provider = env[:box_provider].to_sym if env[:box_provider] box_version = env[:box_version] box_remove_all_versions = env[:box_remove_all_versions] box_remove_all_providers = env[:box_remove_all_providers] box_remove_all_architectures = env[:box_remove_all_architectures] 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 # If there's no box info, then the box doesn't exist here if box_info.empty? raise Errors::BoxRemoveNotFound, name: box_name end # 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, versions: box_info.keys.sort.map { |k| " * #{k}" }.join("\n") end # If a version was provided, make sure it exists if box_version if !box_info.keys.include?(box_version) raise Errors::BoxRemoveVersionNotFound, name: box_name, version: box_version, versions: box_info.keys.sort.map { |k| " * #{k}" }.join("\n") else box_info.delete_if { |k, _| k != box_version } end end # Only a single version remains box_version = box_info.keys.first # Further filtering only matters if not removing all providers if !box_remove_all_providers # If no provider was given, check if there are more # than a single provider for the version if !box_provider && box_info.values.first.size > 1 raise Errors::BoxRemoveMultiProvider, name: box_name, version: box_version, providers: box_info.values.first.keys.map(&:to_s).sort.join(", ") end # If a provider was given, check the version has it if box_provider if !box_info.values.first.key?(box_provider) raise Errors::BoxRemoveProviderNotFound, name: box_name, version: box_version, provider: box_provider.to_s, providers: box_info.values.first.keys.map(&:to_s).sort.join(", ") else box_info.values.first.delete_if { |k, _| k.to_s != box_provider.to_s } end end # Only a single provider remains box_provider = box_info.values.first.keys.first # Further filtering only matters if not removing all architectures if !box_remove_all_architectures # If no architecture was given, check if there are more # than a single architecture for the provider in version if !box_architecture && box_info.values.first.values.first.size > 1 raise Errors::BoxRemoveMultiArchitecture, name: box_name, version: box_version, provider: box_provider.to_s, architectures: box_info.values.first.values.first.sort.join(", ") end # If architecture was given, check the provider for the version has it if box_architecture if !box_info.values.first.values.first.include?(box_architecture) raise Errors::BoxRemoveArchitectureNotFound, name: box_name, version: box_version, provider: box_provider.to_s, architecture: box_architecture, architectures: box_info.values.first.values.first.sort.join(", ") else box_info.values.first.values.first.delete_if { |v| v != box_architecture } end end end end end 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) end end end end end