diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 494f0071b..7778d30ca 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -292,6 +292,10 @@ module Vagrant error_key(:clone_machine_not_found) end + class CommandDeprecated < VagrantError + error_key(:command_deprecated) + end + class CommandUnavailable < VagrantError error_key(:command_unavailable) end diff --git a/lib/vagrant/util.rb b/lib/vagrant/util.rb index 9f86db5a1..d5e328efc 100644 --- a/lib/vagrant/util.rb +++ b/lib/vagrant/util.rb @@ -1,6 +1,7 @@ module Vagrant module Util autoload :Busy, 'vagrant/util/busy' + autoload :CommandDeprecation, 'vagrant/util/command_deprecation' autoload :Counter, 'vagrant/util/counter' autoload :CredentialScrubber, 'vagrant/util/credential_scrubber' autoload :Env, 'vagrant/util/env' diff --git a/lib/vagrant/util/command_deprecation.rb b/lib/vagrant/util/command_deprecation.rb new file mode 100644 index 000000000..62b1a1e54 --- /dev/null +++ b/lib/vagrant/util/command_deprecation.rb @@ -0,0 +1,56 @@ +module Vagrant + module Util + # Automatically add deprecation notices to commands + module CommandDeprecation + + # @return [String] generated name of command + def deprecation_command_name + name_parts = self.class.name.split("::") + [ + name_parts[1].sub('Command', ''), + name_parts[3] + ].compact.map(&:downcase).join(" ") + end + + def self.included(klass) + klass.class_eval do + class << self + if method_defined?(:synopsis) + alias_method :non_deprecated_synopsis, :synopsis + + def synopsis + if !non_deprecated_synopsis.to_s.empty? + "#{non_deprecated_synopsis} [DEPRECATED]" + else + non_deprecated_synopsis + end + end + end + end + alias_method :non_deprecated_execute, :execute + + def execute + @env[:ui].warn(I18n.t("vagrant.commands.deprecated", + name: deprecation_command_name + ) + "\n") + non_deprecated_execute + end + end + end + + # Mark command deprecation complete and fully disable + # the command's functionality + module Complete + def self.included(klass) + klass.include(CommandDeprecation) + klass.class_eval do + def execute + raise Vagrant::Errors::CommandDeprecated, + name: deprecation_command_name + end + end + end + end + end + end +end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 1dc166863..39d67e6fe 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -664,6 +664,9 @@ en: Additionally, the created environment must be started with a provider matching this provider. For example, if you're using VirtualBox, the clone environment must also be using VirtualBox. + command_deprecated: |- + The command 'vagrant %{name}' has been deprecated and is no longer functional + within Vagrant. command_unavailable: |- The executable '%{file}' Vagrant is trying to run was not found in the PATH variable. This is an error. Please verify @@ -1568,6 +1571,9 @@ en: # Translations for commands. e.g. `vagrant x` #------------------------------------------------------------------------------- commands: + deprecated: |- + [DEPRECATION WARNING]: The Vagrant command 'vagrant %{name}' is scheduled be + deprecated in an upcoming Vagrant release. common: vm_already_running: |- VirtualBox VM is already running. diff --git a/test/unit/vagrant/util/command_deprecation_test.rb b/test/unit/vagrant/util/command_deprecation_test.rb new file mode 100644 index 000000000..7c94c9427 --- /dev/null +++ b/test/unit/vagrant/util/command_deprecation_test.rb @@ -0,0 +1,106 @@ +require File.expand_path("../../../base", __FILE__) + +require "vagrant/util/command_deprecation" + +describe Vagrant::Util do + include_context "unit" + + let(:app){ lambda{|env|} } + let(:argv){[]} + let(:env){ {ui: Vagrant::UI::Silent.new} } + + let(:command_class) do + Class.new(Vagrant.plugin("2", :command)) do + def self.synopsis + "base synopsis" + end + def self.name + "VagrantPlugins::CommandTest::Command" + end + def execute + @env[:ui].info("COMMAND CONTENT") + 0 + end + end + end + + let(:command){ command_class.new(app, env) } + + describe Vagrant::Util::CommandDeprecation do + before{ command_class.include(Vagrant::Util::CommandDeprecation) } + + it "should add deprecation warning to synopsis" do + expect(command_class.synopsis).to include('[DEPRECATED]') + command.class.synopsis + end + + it "should add deprecation warning to #execute" do + expect(env[:ui]).to receive(:warn).with(/DEPRECATION WARNING/) + command.execute + end + + it "should execute original command" do + expect(env[:ui]).to receive(:info).with("COMMAND CONTENT") + command.execute + end + + it "should return with a 0 value" do + expect(command.execute).to eq(0) + end + + context "with custom name defined" do + before do + command_class.class_eval do + def deprecation_command_name + "custom-name" + end + end + end + + it "should use custom name within warning message" do + expect(env[:ui]).to receive(:warn).with(/custom-name/) + command.execute + end + end + + context "with deprecated subcommand" do + let(:command_class) do + Class.new(Vagrant.plugin("2", :command)) do + def self.name + "VagrantPlugins::CommandTest::Command::Action" + end + def execute + @env[:ui].info("COMMAND CONTENT") + 0 + end + end + end + + it "should not modify empty synopsis" do + expect(command_class.synopsis.to_s).to be_empty + end + + it "should extract command name and subname" do + expect(command.deprecation_command_name).to eq("test action") + end + end + end + + describe Vagrant::Util::CommandDeprecation::Complete do + before{ command_class.include(Vagrant::Util::CommandDeprecation::Complete) } + + it "should add deprecation warning to synopsis" do + expect(command_class.synopsis).to include('[DEPRECATED]') + command.class.synopsis + end + + it "should raise a deprecation error when executed" do + expect{ command.execute }.to raise_error(Vagrant::Errors::CommandDeprecated) + end + + it "should not run original command" do + expect(env[:ui]).not_to receive(:info).with("COMMAND CONTENT") + expect{ command.execute }.to raise_error(Vagrant::Errors::CommandDeprecated) + end + end +end