diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 623073a97..9edbaf194 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -556,6 +556,18 @@ module Vagrant error_key(:plugin_uninstall_system) end + class PushesNotDefined < VagrantError + error_key(:pushes_not_defined) + end + + class PushStrategyNotDefined < VagrantError + error_key(:push_strategy_not_defined) + end + + class PushStrategyNotProvided < VagrantError + error_key(:push_strategy_not_provided) + end + class RSyncError < VagrantError error_key(:rsync_error) end diff --git a/lib/vagrant/plugin/v2/manager.rb b/lib/vagrant/plugin/v2/manager.rb index 62a23f82a..d8f5296db 100644 --- a/lib/vagrant/plugin/v2/manager.rb +++ b/lib/vagrant/plugin/v2/manager.rb @@ -172,6 +172,17 @@ module Vagrant end end + # This returns all registered pushes. + # + # @return [Registry] + def pushes + Registry.new.tap do |result| + @registered.each do |plugin| + result.merge!(plugin.components.pushes) + end + end + end + # This returns all synced folder implementations. # # @return [Registry] diff --git a/plugins/commands/push/command.rb b/plugins/commands/push/command.rb new file mode 100644 index 000000000..6cbe3452e --- /dev/null +++ b/plugins/commands/push/command.rb @@ -0,0 +1,64 @@ +require 'optparse' + +module VagrantPlugins + module CommandPush + class Command < Vagrant.plugin("2", :command) + def self.synopsis + "deploys code in this environment to a configured destination" + end + + # @todo support multiple strategies if requested by the community + def execute + opts = OptionParser.new do |o| + o.banner = "Usage: vagrant push [strategy] [options]" + end + + # Parse the options + argv = parse_options(opts) + return if !argv + + name, options = argv + pushes = @env.pushes + + validate_pushes!(pushes, name) + + @logger.debug("'push' environment with strategy: `#{name}'") + + @env.push(name) + + 0 + end + + # Validate that the given list of pushes and strategy are valid. + # + # @raise [PushesNotDefined] if there are no pushes defined for the + # environment + # @raise [PushStrategyNotDefined] if a strategy is given, but does not + # correspond to one that exists in the environment + # + # @param [Registry] pushes The list of pushes as a {Registry} + # @param [#to_sym, nil] name The name of the strategy + # + # @return [true] + def validate_pushes!(pushes, name=nil) + if pushes.nil? || pushes.empty? + raise Vagrant::Errors::PushesNotDefined + end + + if name.nil? + if pushes.length != 1 + raise Vagrant::Errors::PushStrategyNotProvided, pushes: pushes + end + else + if !pushes.has_key?(name.to_sym) + raise Vagrant::Errors::PushStrategyNotDefined, + name: name, + pushes: pushes + end + end + + true + end + end + end +end diff --git a/plugins/commands/push/plugin.rb b/plugins/commands/push/plugin.rb new file mode 100644 index 000000000..ecd24dd7a --- /dev/null +++ b/plugins/commands/push/plugin.rb @@ -0,0 +1,17 @@ +require "vagrant" + +module VagrantPlugins + module CommandPush + class Plugin < Vagrant.plugin("2") + name "push command" + description <<-DESC + The `push` command deploys code in this environment. + DESC + + command("push") do + require File.expand_path("../command", __FILE__) + Command + end + end + end +end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index bc3416529..ce9132fd6 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -946,6 +946,23 @@ en: You can however, install a plugin with the same name to replace these plugins. User-installed plugins take priority over system-installed plugins. + pushes_not_defined: |- + The Vagrantfile does not define any 'push' strategies. In order to use + `vagrant push`, you must define at least one push strategy: + + config.push :ftp do |strategy| + # ... strategy-specific options + end + push_strategy_not_defined: |- + The push strategy '%{name}' is not defined in the Vagrantfile. Defined + strategies are: + + %{pushes} + push_strategy_not_provided: |- + The Vagrantfile defines more than one 'push' strategy. Please specify a + strategy. Defined strategies are: + + %{pushes} package_include_symlink: |- A file or directory you're attempting to include with your packaged box has symlinks in it. Vagrant cannot include symlinks in the diff --git a/test/unit/plugins/commands/push/command_test.rb b/test/unit/plugins/commands/push/command_test.rb new file mode 100644 index 000000000..34bb111ea --- /dev/null +++ b/test/unit/plugins/commands/push/command_test.rb @@ -0,0 +1,129 @@ +require File.expand_path("../../../../base", __FILE__) + +require Vagrant.source_root.join("plugins/commands/push/command") + +describe VagrantPlugins::CommandPush::Command do + include_context "unit" + include_context "command plugin helpers" + + def create_registry(items={}) + Vagrant::Registry.new.tap do |registry| + items.each do |k,v| + registry.register(k) { v } + end + end + end + + let(:env) do + isolated_environment.tap do |env| + env.vagrantfile("") + env.create_vagrant_env + end + end + + let(:argv) { [] } + let(:pushes) { create_registry } + + subject { described_class.new(argv, env) } + + before do + Vagrant.plugin("2").manager.stub(pushes: pushes) + end + + describe "#execute" do + before do + allow(subject).to receive(:validate_pushes!) + allow(env).to receive(:pushes) + allow(env).to receive(:push) + end + + it "validates the pushes" do + expect(subject).to receive(:validate_pushes!).once + subject.execute + end + + it "delegates to Environment#push" do + expect(env).to receive(:push).once + subject.execute + end + end + + describe "#validate_pushes!" do + context "when there are no pushes defined" do + let(:pushes) { create_registry } + + context "when a strategy is given" do + it "raises an exception" do + expect { subject.validate_pushes!(pushes, :noop) } + .to raise_error(Vagrant::Errors::PushesNotDefined) + end + end + + context "when no strategy is given" do + it "raises an exception" do + expect { subject.validate_pushes!(pushes) } + .to raise_error(Vagrant::Errors::PushesNotDefined) + end + end + end + + context "when there is one push defined" do + let(:noop) { double("noop") } + let(:pushes) { create_registry(noop: noop) } + + context "when a strategy is given" do + context "when that strategy is not defined" do + it "raises an exception" do + expect { subject.validate_pushes!(pushes, :bacon) } + .to raise_error(Vagrant::Errors::PushStrategyNotDefined) + end + end + + context "when that strategy is defined" do + it "does not raise an exception" do + expect { subject.validate_pushes!(pushes, :noop) } + .to_not raise_error + end + end + end + + context "when no strategy is given" do + it "does not raise an exception" do + expect { subject.validate_pushes!(pushes) } + .to_not raise_error + end + end + end + + context "when there are multiple pushes defined" do + let(:noop) { double("noop") } + let(:ftp) { double("ftp") } + let(:pushes) { create_registry(noop: noop, ftp: ftp) } + + context "when a strategy is given" do + context "when that strategy is not defined" do + it "raises an exception" do + expect { subject.validate_pushes!(pushes, :bacon) } + .to raise_error(Vagrant::Errors::PushStrategyNotDefined) + end + end + + context "when that strategy is defined" do + it "does not raise an exception" do + expect { subject.validate_pushes!(pushes, :noop) } + .to_not raise_error + expect { subject.validate_pushes!(pushes, :ftp) } + .to_not raise_error + end + end + end + + context "when no strategy is given" do + it "raises an exception" do + expect { subject.validate_pushes!(pushes) } + .to raise_error(Vagrant::Errors::PushStrategyNotProvided) + end + end + end + end +end