From a93fff103f90cdad7f12eb2af129ffb8cde93434 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 22 Oct 2014 17:39:45 -0400 Subject: [PATCH 01/83] Add base skeleton for `vagrant push` --- lib/vagrant/plugin/v2.rb | 1 + lib/vagrant/plugin/v2/plugin.rb | 13 +++++++++++++ lib/vagrant/plugin/v2/push.rb | 31 +++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 lib/vagrant/plugin/v2/push.rb diff --git a/lib/vagrant/plugin/v2.rb b/lib/vagrant/plugin/v2.rb index 1539667bd..953f73ff6 100644 --- a/lib/vagrant/plugin/v2.rb +++ b/lib/vagrant/plugin/v2.rb @@ -16,6 +16,7 @@ module Vagrant autoload :Manager, "vagrant/plugin/v2/manager" autoload :Plugin, "vagrant/plugin/v2/plugin" autoload :Provider, "vagrant/plugin/v2/provider" + autoload :Push, "vagrant/plugin/v2/push" autoload :Provisioner, "vagrant/plugin/v2/provisioner" autoload :SyncedFolder, "vagrant/plugin/v2/synced_folder" end diff --git a/lib/vagrant/plugin/v2/plugin.rb b/lib/vagrant/plugin/v2/plugin.rb index 9a7f6177d..e6020f6c5 100644 --- a/lib/vagrant/plugin/v2/plugin.rb +++ b/lib/vagrant/plugin/v2/plugin.rb @@ -221,6 +221,19 @@ module Vagrant data[:provisioners] end + # Registers additional pushes to be available. + # + # @param [String] name Name of the push. + def self.push(name=UNSET_VALUE, &block) + data[:pushes] ||= Registry.new + + # Register a new pusher class only if a name was given + data[:pushes].register(name.to_sym, &block) if name != UNSET_VALUE + + # Return the registry + data[:pushes] + end + # Registers additional synced folder implementations. # # @param [String] name Name of the implementation. diff --git a/lib/vagrant/plugin/v2/push.rb b/lib/vagrant/plugin/v2/push.rb new file mode 100644 index 000000000..63f9d4ae0 --- /dev/null +++ b/lib/vagrant/plugin/v2/push.rb @@ -0,0 +1,31 @@ +module Vagrant + module Plugin + module V2 + class Push + attr_reader :machine + attr_reader :config + + # Initializes the pusher with the machine that exists for this project + # as well as the configuration (the push configuration, not the full machine + # configuration). + # + # The pusher should _not_ do anything at this point except + # initialize internal state. + # + # @param [Machine] machine The machine associated with this code. + # @param [Object] config Push configuration, if one was set. + def initialize(machine, config) + @machine = machine + @config = config + end + + # This is the method called when the actual pushing should be + # done. + # + # No return value is expected. + def push + end + end + end + end +end From 8a7e546972bb0b613804edc16f3e6914d6b803c5 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 22 Oct 2014 17:40:08 -0400 Subject: [PATCH 02/83] Add preliminary File pusher (incomplete) --- plugins/pushes/file/config.rb | 26 +++++++++++++++ plugins/pushes/file/plugin.rb | 23 +++++++++++++ plugins/pushes/file/push.rb | 29 +++++++++++++++++ templates/locales/en.yml | 4 +++ test/unit/plugins/pushes/file/config_test.rb | 34 ++++++++++++++++++++ 5 files changed, 116 insertions(+) create mode 100644 plugins/pushes/file/config.rb create mode 100644 plugins/pushes/file/plugin.rb create mode 100644 plugins/pushes/file/push.rb create mode 100644 test/unit/plugins/pushes/file/config_test.rb diff --git a/plugins/pushes/file/config.rb b/plugins/pushes/file/config.rb new file mode 100644 index 000000000..025ab78dd --- /dev/null +++ b/plugins/pushes/file/config.rb @@ -0,0 +1,26 @@ +module VagrantPlugins + module FileDeploy + class Config < Vagrant.plugin("2", :config) + attr_accessor :destination + + def initialize + @destination = UNSET_VALUE + end + + def finalize! + @destination = nil if @destination == UNSET_VALUE + end + + def validate(machine) + errors = _detected_errors + + # Validate that a destination was provided + if !destination + errors << I18n.t("vagrant.pushes.file.no_destination") + end + + { "File push" => errors } + end + end + end +end diff --git a/plugins/pushes/file/plugin.rb b/plugins/pushes/file/plugin.rb new file mode 100644 index 000000000..365e5e44c --- /dev/null +++ b/plugins/pushes/file/plugin.rb @@ -0,0 +1,23 @@ +require "vagrant" + +module VagrantPlugins + module FileDeploy + class Plugin < Vagrant.plugin("2") + name "file" + description <<-DESC + Deploy by pushing to a filepath on your local system or a remote share + attached to this system + DESC + + config(:file, :push) do + require File.expand_path("../config", __FILE__) + Config + end + + push(:file) do + require File.expand_path("../push", __FILE__) + Push + end + end + end +end diff --git a/plugins/pushes/file/push.rb b/plugins/pushes/file/push.rb new file mode 100644 index 000000000..662719ec5 --- /dev/null +++ b/plugins/pushes/file/push.rb @@ -0,0 +1,29 @@ +module VagrantPlugins + module FileDeploy + class Push < Vagrant.plugin("2", :push) + def push + @machine.communicate.tap do |comm| + destination = expand_guest_path(config.destination) + + # Make sure the remote path exists + command = "mkdir -p %s" % File.dirname(destination) + comm.execute(command) + + # Now push the deploy... + # ??? + end + end + + private + + # Expand the guest path if the guest has the capability + def expand_guest_path(destination) + if machine.guest.capability?(:shell_expand_guest_path) + machine.guest.capability(:shell_expand_guest_path, destination) + else + destination + end + end + end + end +end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 72124f7e9..bc3416529 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1892,3 +1892,7 @@ en: You must include both public and private keys. must_accept_keys: |- You must accept keys when running highstate with master! + + pushes: + file: + no_destination: "File destination must be specified." diff --git a/test/unit/plugins/pushes/file/config_test.rb b/test/unit/plugins/pushes/file/config_test.rb new file mode 100644 index 000000000..c5c7c591e --- /dev/null +++ b/test/unit/plugins/pushes/file/config_test.rb @@ -0,0 +1,34 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/pushes/file/config") + +describe VagrantPlugins::FileDeploy::Config do + include_context "unit" + + subject { described_class.new } + + let(:machine) { double("machine") } + + describe "#validate" do + it "returns an error if destination is not specified" do + subject.finalize! + + result = subject.validate(machine) + + expect(result["File push"]).to eql([ + I18n.t("vagrant.pushes.file.no_destination") + ]) + end + + it "returns no errors when the config is valid" do + existing_file = File.expand_path(__FILE__) + + subject.destination = existing_file + subject.finalize! + + result = subject.validate(machine) + + expect(result["File push"]).to be_empty + end + end +end From 87b4e1f2ccc45fac688dfe3dd3248f087c59216e Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 22 Oct 2014 22:02:46 -0400 Subject: [PATCH 03/83] Accept an environment in the push config --- lib/vagrant/plugin/v2/push.rb | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/lib/vagrant/plugin/v2/push.rb b/lib/vagrant/plugin/v2/push.rb index 63f9d4ae0..002479b33 100644 --- a/lib/vagrant/plugin/v2/push.rb +++ b/lib/vagrant/plugin/v2/push.rb @@ -2,20 +2,16 @@ module Vagrant module Plugin module V2 class Push - attr_reader :machine + attr_reader :environment attr_reader :config - # Initializes the pusher with the machine that exists for this project - # as well as the configuration (the push configuration, not the full machine - # configuration). + # Initializes the pusher with the given environment the push + # configuration. # - # The pusher should _not_ do anything at this point except - # initialize internal state. - # - # @param [Machine] machine The machine associated with this code. - # @param [Object] config Push configuration, if one was set. - def initialize(machine, config) - @machine = machine + # @param [environment] environment + # @param [Object] config Push configuration + def initialize(environment, config) + @environment = environment @config = config end From 5b9240ad8a52e1c70638a28a4431f1dc65d1e4ee Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 22 Oct 2014 22:02:57 -0400 Subject: [PATCH 04/83] Add Push to the PLUGIN_COMPONENTS --- lib/vagrant.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/vagrant.rb b/lib/vagrant.rb index d3ab6f37c..4b5dc84ba 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -123,6 +123,7 @@ module Vagrant c.register([:"2", :host]) { Plugin::V2::Host } c.register([:"2", :provider]) { Plugin::V2::Provider } c.register([:"2", :provisioner]) { Plugin::V2::Provisioner } + c.register([:"2", :push]) { Plugin::V2::Push } c.register([:"2", :synced_folder]) { Plugin::V2::SyncedFolder } end From 72affa0a10ba65edc27a4c48212a1183fd3ce7db Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 22 Oct 2014 22:03:11 -0400 Subject: [PATCH 05/83] Accept a list of options in #push signature --- lib/vagrant/plugin/v2/plugin.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/vagrant/plugin/v2/plugin.rb b/lib/vagrant/plugin/v2/plugin.rb index e6020f6c5..9b09d94b0 100644 --- a/lib/vagrant/plugin/v2/plugin.rb +++ b/lib/vagrant/plugin/v2/plugin.rb @@ -224,11 +224,13 @@ module Vagrant # Registers additional pushes to be available. # # @param [String] name Name of the push. - def self.push(name=UNSET_VALUE, &block) + def self.push(name=UNSET_VALUE, options=nil, &block) data[:pushes] ||= Registry.new # Register a new pusher class only if a name was given - data[:pushes].register(name.to_sym, &block) if name != UNSET_VALUE + if name != UNSET_VALUE + data[:pushes].register(name.to_sym) { [block.call, options] } + end # Return the registry data[:pushes] From 0e824cc47193047ad389226b1c8c3f5ca53343d9 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 22 Oct 2014 22:29:23 -0400 Subject: [PATCH 06/83] Rename file push to noop push --- plugins/pushes/file/config.rb | 26 --------------- plugins/pushes/file/push.rb | 29 ----------------- plugins/pushes/noop/config.rb | 16 +++++++++ plugins/pushes/{file => noop}/plugin.rb | 11 +++---- plugins/pushes/noop/push.rb | 11 +++++++ test/unit/plugins/pushes/file/config_test.rb | 34 -------------------- test/unit/plugins/pushes/noop/config_test.rb | 14 ++++++++ 7 files changed, 46 insertions(+), 95 deletions(-) delete mode 100644 plugins/pushes/file/config.rb delete mode 100644 plugins/pushes/file/push.rb create mode 100644 plugins/pushes/noop/config.rb rename plugins/pushes/{file => noop}/plugin.rb (60%) create mode 100644 plugins/pushes/noop/push.rb delete mode 100644 test/unit/plugins/pushes/file/config_test.rb create mode 100644 test/unit/plugins/pushes/noop/config_test.rb diff --git a/plugins/pushes/file/config.rb b/plugins/pushes/file/config.rb deleted file mode 100644 index 025ab78dd..000000000 --- a/plugins/pushes/file/config.rb +++ /dev/null @@ -1,26 +0,0 @@ -module VagrantPlugins - module FileDeploy - class Config < Vagrant.plugin("2", :config) - attr_accessor :destination - - def initialize - @destination = UNSET_VALUE - end - - def finalize! - @destination = nil if @destination == UNSET_VALUE - end - - def validate(machine) - errors = _detected_errors - - # Validate that a destination was provided - if !destination - errors << I18n.t("vagrant.pushes.file.no_destination") - end - - { "File push" => errors } - end - end - end -end diff --git a/plugins/pushes/file/push.rb b/plugins/pushes/file/push.rb deleted file mode 100644 index 662719ec5..000000000 --- a/plugins/pushes/file/push.rb +++ /dev/null @@ -1,29 +0,0 @@ -module VagrantPlugins - module FileDeploy - class Push < Vagrant.plugin("2", :push) - def push - @machine.communicate.tap do |comm| - destination = expand_guest_path(config.destination) - - # Make sure the remote path exists - command = "mkdir -p %s" % File.dirname(destination) - comm.execute(command) - - # Now push the deploy... - # ??? - end - end - - private - - # Expand the guest path if the guest has the capability - def expand_guest_path(destination) - if machine.guest.capability?(:shell_expand_guest_path) - machine.guest.capability(:shell_expand_guest_path, destination) - else - destination - end - end - end - end -end diff --git a/plugins/pushes/noop/config.rb b/plugins/pushes/noop/config.rb new file mode 100644 index 000000000..9b23fb450 --- /dev/null +++ b/plugins/pushes/noop/config.rb @@ -0,0 +1,16 @@ +module VagrantPlugins + module NoopDeploy + class Config < Vagrant.plugin("2", :config) + def initialize + end + + def finalize! + end + + def validate(machine) + errors = _detected_errors + { "Noop push" => errors } + end + end + end +end diff --git a/plugins/pushes/file/plugin.rb b/plugins/pushes/noop/plugin.rb similarity index 60% rename from plugins/pushes/file/plugin.rb rename to plugins/pushes/noop/plugin.rb index 365e5e44c..d3dc6994d 100644 --- a/plugins/pushes/file/plugin.rb +++ b/plugins/pushes/noop/plugin.rb @@ -1,20 +1,19 @@ require "vagrant" module VagrantPlugins - module FileDeploy + module NoopDeploy class Plugin < Vagrant.plugin("2") - name "file" + name "noop" description <<-DESC - Deploy by pushing to a filepath on your local system or a remote share - attached to this system + Literally do nothing DESC - config(:file, :push) do + config(:noop, :push) do require File.expand_path("../config", __FILE__) Config end - push(:file) do + push(:noop) do require File.expand_path("../push", __FILE__) Push end diff --git a/plugins/pushes/noop/push.rb b/plugins/pushes/noop/push.rb new file mode 100644 index 000000000..490e32188 --- /dev/null +++ b/plugins/pushes/noop/push.rb @@ -0,0 +1,11 @@ +module VagrantPlugins + module NoopDeploy + class Push < Vagrant.plugin("2", :push) + def push + @machine.communicate.tap do |comm| + puts "pushed" + end + end + end + end +end diff --git a/test/unit/plugins/pushes/file/config_test.rb b/test/unit/plugins/pushes/file/config_test.rb deleted file mode 100644 index c5c7c591e..000000000 --- a/test/unit/plugins/pushes/file/config_test.rb +++ /dev/null @@ -1,34 +0,0 @@ -require_relative "../../../base" - -require Vagrant.source_root.join("plugins/pushes/file/config") - -describe VagrantPlugins::FileDeploy::Config do - include_context "unit" - - subject { described_class.new } - - let(:machine) { double("machine") } - - describe "#validate" do - it "returns an error if destination is not specified" do - subject.finalize! - - result = subject.validate(machine) - - expect(result["File push"]).to eql([ - I18n.t("vagrant.pushes.file.no_destination") - ]) - end - - it "returns no errors when the config is valid" do - existing_file = File.expand_path(__FILE__) - - subject.destination = existing_file - subject.finalize! - - result = subject.validate(machine) - - expect(result["File push"]).to be_empty - end - end -end diff --git a/test/unit/plugins/pushes/noop/config_test.rb b/test/unit/plugins/pushes/noop/config_test.rb new file mode 100644 index 000000000..05be6c3f8 --- /dev/null +++ b/test/unit/plugins/pushes/noop/config_test.rb @@ -0,0 +1,14 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/pushes/noop/config") + +describe VagrantPlugins::NoopDeploy::Config do + include_context "unit" + + subject { described_class.new } + + let(:machine) { double("machine") } + + describe "#validate" do + end +end From 60a847289190075144fff8dd3d842de4a738503c Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 22 Oct 2014 22:29:39 -0400 Subject: [PATCH 07/83] Use a pushes registry instead of data hash --- lib/vagrant/plugin/v2/components.rb | 6 ++++ lib/vagrant/plugin/v2/plugin.rb | 11 +++---- test/unit/vagrant/plugin/v2/plugin_test.rb | 35 ++++++++++++++++++++++ 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/lib/vagrant/plugin/v2/components.rb b/lib/vagrant/plugin/v2/components.rb index 7bae6c29a..d7c64d370 100644 --- a/lib/vagrant/plugin/v2/components.rb +++ b/lib/vagrant/plugin/v2/components.rb @@ -54,6 +54,11 @@ module Vagrant # @return [Hash] attr_reader :provider_capabilities + # This contains all the push implementations by name. + # + # @return [Registry>] + attr_reader :pushes + # This contains all the synced folder implementations by name. # # @return [Registry>] @@ -71,6 +76,7 @@ module Vagrant @host_capabilities = Hash.new { |h, k| h[k] = Registry.new } @providers = Registry.new @provider_capabilities = Hash.new { |h, k| h[k] = Registry.new } + @pushes = Registry.new @synced_folders = Registry.new end end diff --git a/lib/vagrant/plugin/v2/plugin.rb b/lib/vagrant/plugin/v2/plugin.rb index 9b09d94b0..bcae23eaa 100644 --- a/lib/vagrant/plugin/v2/plugin.rb +++ b/lib/vagrant/plugin/v2/plugin.rb @@ -224,16 +224,13 @@ module Vagrant # Registers additional pushes to be available. # # @param [String] name Name of the push. + # @param [Hash] options List of options for the push. def self.push(name=UNSET_VALUE, options=nil, &block) - data[:pushes] ||= Registry.new - - # Register a new pusher class only if a name was given - if name != UNSET_VALUE - data[:pushes].register(name.to_sym) { [block.call, options] } + components.pushes.register(name.to_sym) do + [block.call, options] end - # Return the registry - data[:pushes] + nil end # Registers additional synced folder implementations. diff --git a/test/unit/vagrant/plugin/v2/plugin_test.rb b/test/unit/vagrant/plugin/v2/plugin_test.rb index 944d38f9f..442462805 100644 --- a/test/unit/vagrant/plugin/v2/plugin_test.rb +++ b/test/unit/vagrant/plugin/v2/plugin_test.rb @@ -322,6 +322,41 @@ describe Vagrant::Plugin::V2::Plugin do end end + describe "pushes" do + it "should register implementations" do + plugin = Class.new(described_class) do + push("foo") { "bar" } + end + + expect(plugin.components.pushes[:foo]).to eq(["bar", nil]) + end + + it "should be able to specify priorities" do + plugin = Class.new(described_class) do + push("foo", bar: 1) { "bar" } + end + + expect(plugin.components.pushes[:foo]).to eq(["bar", bar: 1]) + end + + it "should lazily register implementations" do + # Below would raise an error if the value of the config class was + # evaluated immediately. By asserting that this does not raise an + # error, we verify that the value is actually lazily loaded + plugin = nil + expect { + plugin = Class.new(described_class) do + push("foo") { raise StandardError, "FAIL!" } + end + }.to_not raise_error + + # Now verify when we actually get the configuration key that + # a proper error is raised. + expect { + plugin.components.pushes[:foo] + }.to raise_error(StandardError) + end + end describe "synced folders" do it "should register implementations" do plugin = Class.new(described_class) do From bc4bbb9fc0973b1efc59cf95d60bd1521815133e Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Thu, 23 Oct 2014 11:17:23 -0400 Subject: [PATCH 08/83] Add #length and #size methods to Registry --- lib/vagrant/registry.rb | 10 +++++++++- test/unit/vagrant/registry_test.rb | 22 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/lib/vagrant/registry.rb b/lib/vagrant/registry.rb index 5095f2097..f26e17d4f 100644 --- a/lib/vagrant/registry.rb +++ b/lib/vagrant/registry.rb @@ -34,7 +34,7 @@ module Vagrant def has_key?(key) @items.has_key?(key) end - + # Returns an array populated with the keys of this object. # # @return [Array] @@ -49,6 +49,14 @@ module Vagrant end end + # Return the number of elements in this registry. + # + # @return [Fixnum] + def length + @items.keys.length + end + alias_method :size, :length + # Merge one registry with another and return a completely new # registry. Note that the result cache is completely busted, so # any gets on the new registry will result in a cache miss. diff --git a/test/unit/vagrant/registry_test.rb b/test/unit/vagrant/registry_test.rb index d12f46ffe..3a33e9634 100644 --- a/test/unit/vagrant/registry_test.rb +++ b/test/unit/vagrant/registry_test.rb @@ -90,6 +90,28 @@ describe Vagrant::Registry do expect(result["bar"]).to eq("barvalue") end + describe "#length" do + it "should return 0 when the registry is empty" do + expect(instance.length).to eq(0) + end + + it "should return the number of items in the registry" do + instance.register("foo") { } + instance.register("bar") { } + + expect(instance.length).to eq(2) + end + end + + describe "#size" do + it "should be an alias to #length" do + size = described_class.instance_method(:size) + length = described_class.instance_method(:length) + + expect(size).to eq(length) + end + end + describe "merging" do it "should merge in another registry" do one = described_class.new From 2b03838fba4fb5be296c81af869f0572bfccdcc2 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Thu, 23 Oct 2014 11:23:55 -0400 Subject: [PATCH 09/83] Make Registry enumerable Registry already responds to #each, so including the Enumerable module gives us nice methods like #select and #collect fo' free! --- lib/vagrant/registry.rb | 2 ++ test/unit/vagrant/registry_test.rb | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/lib/vagrant/registry.rb b/lib/vagrant/registry.rb index f26e17d4f..4e401777b 100644 --- a/lib/vagrant/registry.rb +++ b/lib/vagrant/registry.rb @@ -4,6 +4,8 @@ module Vagrant # This allows certain components (such as guest systems, configuration # pieces, etc.) to be registered and queried, lazily. class Registry + include Enumerable + def initialize @items = {} @results_cache = {} diff --git a/test/unit/vagrant/registry_test.rb b/test/unit/vagrant/registry_test.rb index 3a33e9634..94364404c 100644 --- a/test/unit/vagrant/registry_test.rb +++ b/test/unit/vagrant/registry_test.rb @@ -3,6 +3,10 @@ require File.expand_path("../../base", __FILE__) describe Vagrant::Registry do let(:instance) { described_class.new } + it "should include enumerable" do + expect(instance).to be_a(Enumerable) + end + it "should return nil for nonexistent items" do expect(instance.get("foo")).to be_nil end From c0b107ff6975b3f181dd9e3d8dbcb1e900c3336d Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Thu, 23 Oct 2014 11:24:13 -0400 Subject: [PATCH 10/83] Add Registry#empty? to check if a registry has any items --- lib/vagrant/registry.rb | 7 +++++++ test/unit/vagrant/registry_test.rb | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/lib/vagrant/registry.rb b/lib/vagrant/registry.rb index 4e401777b..bb8521f74 100644 --- a/lib/vagrant/registry.rb +++ b/lib/vagrant/registry.rb @@ -59,6 +59,13 @@ module Vagrant end alias_method :size, :length + # Checks if this registry has any items. + # + # @return [Boolean] + def empty? + @items.keys.empty? + end + # Merge one registry with another and return a completely new # registry. Note that the result cache is completely busted, so # any gets on the new registry will result in a cache miss. diff --git a/test/unit/vagrant/registry_test.rb b/test/unit/vagrant/registry_test.rb index 94364404c..12ccbec5c 100644 --- a/test/unit/vagrant/registry_test.rb +++ b/test/unit/vagrant/registry_test.rb @@ -116,6 +116,17 @@ describe Vagrant::Registry do end end + describe "#empty" do + it "should return true when the registry is empty" do + expect(instance.empty?).to be(true) + end + + it "should return false when there is at least one element" do + instance.register("foo") { } + expect(instance.empty?).to be(false) + end + end + describe "merging" do it "should merge in another registry" do one = described_class.new From d79a0d52ddcf19074155ebedeca804811653fa38 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Thu, 23 Oct 2014 13:42:02 -0400 Subject: [PATCH 11/83] Do not use Enumerable in Registry Calling methods like #first in Registry is misleading because it returns a different result than registry.get(registry.keys.first). --- lib/vagrant/registry.rb | 2 -- test/unit/vagrant/registry_test.rb | 4 ---- 2 files changed, 6 deletions(-) diff --git a/lib/vagrant/registry.rb b/lib/vagrant/registry.rb index bb8521f74..f3c86edec 100644 --- a/lib/vagrant/registry.rb +++ b/lib/vagrant/registry.rb @@ -4,8 +4,6 @@ module Vagrant # This allows certain components (such as guest systems, configuration # pieces, etc.) to be registered and queried, lazily. class Registry - include Enumerable - def initialize @items = {} @results_cache = {} diff --git a/test/unit/vagrant/registry_test.rb b/test/unit/vagrant/registry_test.rb index 12ccbec5c..f177a6a1c 100644 --- a/test/unit/vagrant/registry_test.rb +++ b/test/unit/vagrant/registry_test.rb @@ -3,10 +3,6 @@ require File.expand_path("../../base", __FILE__) describe Vagrant::Registry do let(:instance) { described_class.new } - it "should include enumerable" do - expect(instance).to be_a(Enumerable) - end - it "should return nil for nonexistent items" do expect(instance.get("foo")).to be_nil end From b6c5ca6b7ad12f03830c009195eb76f0d78a18e4 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Thu, 23 Oct 2014 13:51:18 -0400 Subject: [PATCH 12/83] Add Push command and tests --- lib/vagrant/errors.rb | 12 ++ lib/vagrant/plugin/v2/manager.rb | 11 ++ plugins/commands/push/command.rb | 64 +++++++++ plugins/commands/push/plugin.rb | 17 +++ templates/locales/en.yml | 17 +++ .../plugins/commands/push/command_test.rb | 129 ++++++++++++++++++ 6 files changed, 250 insertions(+) create mode 100644 plugins/commands/push/command.rb create mode 100644 plugins/commands/push/plugin.rb create mode 100644 test/unit/plugins/commands/push/command_test.rb 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 From 03b81055710082fd92625bdc8018662a39eb973f Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Thu, 23 Oct 2014 16:01:43 -0400 Subject: [PATCH 13/83] Super primitive implementation of Environment#push --- lib/vagrant/environment.rb | 45 +++++++++ lib/vagrant/errors.rb | 4 + lib/vagrant/vagrantfile.rb | 14 +++ plugins/commands/push/command.rb | 7 +- plugins/kernel_v2/config/push.rb | 99 +++++++++++++++++++ plugins/kernel_v2/plugin.rb | 5 + templates/locales/en.yml | 11 ++- .../plugins/commands/push/command_test.rb | 16 +-- 8 files changed, 184 insertions(+), 17 deletions(-) create mode 100644 plugins/kernel_v2/config/push.rb diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 4f087a103..f89c94773 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -539,6 +539,51 @@ module Vagrant end end + # This executes the push with the given name, raising any exceptions that + # occur. + # + def push(name=nil) + @logger.info("Getting push: #{name}") + + if vagrantfile.pushes.nil? || vagrantfile.pushes.empty? + raise Vagrant::Errors::PushesNotDefined + end + + if name.nil? + if vagrantfile.pushes.length != 1 + raise Vagrant::Errors::PushStrategyNotProvided, + pushes: vagrantfile.pushes + end + name = vagrantfile.pushes.first + else + if !vagrantfile.pushes.include?(name.to_sym) + raise Vagrant::Errors::PushStrategyNotDefined, + name: name, + pushes: vagrantfile.pushes + end + end + + push_registry = Vagrant.plugin("2").manager.pushes + + push_config = vagrantfile.push(name) + push_config.each do |strategy, config_blocks| + plugin, options = push_registry.get(strategy) + + # TODO: What do we do with options? + # options + + if plugin.nil? + raise Vagrant::Errors::PushStrategyNotLoaded, + name: strategy, + pushes: push_registry.keys + end + + # TODO: This should take a plugin configuration, not a list of config + # blocks, or should it? + plugin.new(self, config_blocks).push + end + end + # This returns a machine with the proper provider for this environment. # The machine named by `name` must be in this environment. # diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 9edbaf194..de002ffed 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -564,6 +564,10 @@ module Vagrant error_key(:push_strategy_not_defined) end + class PushStrategyNotLoaded < VagrantError + error_key(:push_strategy_not_loaded) + end + class PushStrategyNotProvided < VagrantError error_key(:push_strategy_not_provided) end diff --git a/lib/vagrant/vagrantfile.rb b/lib/vagrant/vagrantfile.rb index 011d16351..7467ffbbc 100644 --- a/lib/vagrant/vagrantfile.rb +++ b/lib/vagrant/vagrantfile.rb @@ -247,6 +247,20 @@ module Vagrant nil end + # Returns the list of defined pushes in this Vagrantfile. + # + # @return [Array] + def pushes + @config.push.defined_pushes + end + + # Get the push by the given name. + # + # @return [idk] + def push(name) + @config.push.get_push(name) + end + protected def find_vagrantfile(search_path) diff --git a/plugins/commands/push/command.rb b/plugins/commands/push/command.rb index 6cbe3452e..ec7324b55 100644 --- a/plugins/commands/push/command.rb +++ b/plugins/commands/push/command.rb @@ -17,13 +17,14 @@ module VagrantPlugins argv = parse_options(opts) return if !argv - name, options = argv + name = argv[0] pushes = @env.pushes + # TODO: this logic is 100% duplicated in Enviroment#push - should we + # just not validate here? validate_pushes!(pushes, name) @logger.debug("'push' environment with strategy: `#{name}'") - @env.push(name) 0 @@ -50,7 +51,7 @@ module VagrantPlugins raise Vagrant::Errors::PushStrategyNotProvided, pushes: pushes end else - if !pushes.has_key?(name.to_sym) + if !pushes.key?(name.to_sym) raise Vagrant::Errors::PushStrategyNotDefined, name: name, pushes: pushes diff --git a/plugins/kernel_v2/config/push.rb b/plugins/kernel_v2/config/push.rb new file mode 100644 index 000000000..c5c3a10b0 --- /dev/null +++ b/plugins/kernel_v2/config/push.rb @@ -0,0 +1,99 @@ +require "vagrant" + +module VagrantPlugins + module Kernel_V2 + class PushConfig < Vagrant.plugin("2", :config) + VALID_OPTIONS = [:strategy].freeze + + attr_accessor :name + + def initialize + # Internal state + @__defined_pushes = {} + @__finalized = false + end + + def finalize! + @__finalized = true + end + + # Define a new push in the Vagrantfile with the given name. + # + # @example + # vm.push.define "ftp" + # + # @example + # vm.push.define "ftp" do |s| + # s.host = "..." + # end + # + # @example + # vm.push.define "production", strategy: "docker" do |s| + # # ... + # end + # + # @param [#to_sym] name The name of the this strategy. By default, this + # is also the name of the strategy, but the `:strategy` key can be given + # to customize this behavior + # @param [Hash] options The list of options + # + def define(name, **options, &block) + validate_options!(options) + + name = name.to_sym + strategy = options[:strategy] || name + + @__defined_pushes[name] ||= [] + @__defined_pushes[name] << [strategy.to_sym, block] + end + + # The String representation of this Push. + # + # @return [String] + def to_s + "Push" + end + + # Custom merge method + def merge(other) + super.tap do |result| + other_pushes = other.instance_variable_get(:@__defined_pushes) + new_pushes = @__defined_pushes.dup + + other_pushes.each do |key, tuples| + new_pushes[key] ||= [] + new_pushes[key] += tuples + end + + result.instance_variable_set(:@__defined_pushes, new_pushes) + end + end + + # This returns the list of pushes defined in the Vagrantfile. + # + # @return [Array] + def defined_pushes + raise "Must finalize first!" if !@__finalized + @__defined_pushes.keys + end + + # This returns the compiled push-specific configuration for the given + # provider. + # + # @param [#to_sym] name Name of the push + def get_push(name) + raise "Must finalize first!" if !@__finalized + @__defined_pushes[name.to_sym] + end + + private + + def validate_options!(options) + extra_keys = VALID_OPTIONS - options.keys + if !extra_keys.empty? + raise "Invalid option(s): #{extra_keys.join(", ")}" + end + end + end + end +end diff --git a/plugins/kernel_v2/plugin.rb b/plugins/kernel_v2/plugin.rb index 0904481df..27737854f 100644 --- a/plugins/kernel_v2/plugin.rb +++ b/plugins/kernel_v2/plugin.rb @@ -25,6 +25,11 @@ module VagrantPlugins PackageConfig end + config("push") do + require File.expand_path("../config/push", __FILE__) + PushConfig + end + config("vagrant") do require File.expand_path("../config/vagrant", __FILE__) VagrantConfig diff --git a/templates/locales/en.yml b/templates/locales/en.yml index ce9132fd6..fa3c76414 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -955,12 +955,19 @@ en: end push_strategy_not_defined: |- The push strategy '%{name}' is not defined in the Vagrantfile. Defined - strategies are: + strategy names are: + + %{pushes} + + push_strategy_not_defined: |- + There are no push strategies named '%{name}'. Please make sure you + spelled it correctly. If you are using an external push strategy, you + may need to install a plugin. Loaded push strategies are: %{pushes} push_strategy_not_provided: |- The Vagrantfile defines more than one 'push' strategy. Please specify a - strategy. Defined strategies are: + strategy. Defined strategy names are: %{pushes} package_include_symlink: |- diff --git a/test/unit/plugins/commands/push/command_test.rb b/test/unit/plugins/commands/push/command_test.rb index 34bb111ea..85be81766 100644 --- a/test/unit/plugins/commands/push/command_test.rb +++ b/test/unit/plugins/commands/push/command_test.rb @@ -6,14 +6,6 @@ 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("") @@ -22,7 +14,7 @@ describe VagrantPlugins::CommandPush::Command do end let(:argv) { [] } - let(:pushes) { create_registry } + let(:pushes) { {} } subject { described_class.new(argv, env) } @@ -50,7 +42,7 @@ describe VagrantPlugins::CommandPush::Command do describe "#validate_pushes!" do context "when there are no pushes defined" do - let(:pushes) { create_registry } + let(:pushes) { {} } context "when a strategy is given" do it "raises an exception" do @@ -69,7 +61,7 @@ describe VagrantPlugins::CommandPush::Command do context "when there is one push defined" do let(:noop) { double("noop") } - let(:pushes) { create_registry(noop: noop) } + let(:pushes) { { noop: noop } } context "when a strategy is given" do context "when that strategy is not defined" do @@ -98,7 +90,7 @@ describe VagrantPlugins::CommandPush::Command do context "when there are multiple pushes defined" do let(:noop) { double("noop") } let(:ftp) { double("ftp") } - let(:pushes) { create_registry(noop: noop, ftp: ftp) } + let(:pushes) { { noop: noop, ftp: ftp } } context "when a strategy is given" do context "when that strategy is not defined" do From 190da2640492f4bfe70ca3e3908425f0dfbcdc4a Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Thu, 23 Oct 2014 16:02:51 -0400 Subject: [PATCH 14/83] Push does not have access to @machine --- plugins/pushes/noop/push.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/pushes/noop/push.rb b/plugins/pushes/noop/push.rb index 490e32188..537ea1c7d 100644 --- a/plugins/pushes/noop/push.rb +++ b/plugins/pushes/noop/push.rb @@ -2,9 +2,7 @@ module VagrantPlugins module NoopDeploy class Push < Vagrant.plugin("2", :push) def push - @machine.communicate.tap do |comm| - puts "pushed" - end + puts "pushed" end end end From 988518a6ba0deea79ead95a30dbcb33d27a00c5b Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Thu, 23 Oct 2014 16:06:25 -0400 Subject: [PATCH 15/83] Make Environment#pushes its own method --- lib/vagrant/environment.rb | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index f89c94773..106d0d9c5 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -545,21 +545,21 @@ module Vagrant def push(name=nil) @logger.info("Getting push: #{name}") - if vagrantfile.pushes.nil? || vagrantfile.pushes.empty? + if pushes.nil? || pushes.empty? raise Vagrant::Errors::PushesNotDefined end if name.nil? - if vagrantfile.pushes.length != 1 + if pushes.length != 1 raise Vagrant::Errors::PushStrategyNotProvided, - pushes: vagrantfile.pushes + pushes: pushes end - name = vagrantfile.pushes.first + name = pushes.first else - if !vagrantfile.pushes.include?(name.to_sym) + if !pushes.include?(name.to_sym) raise Vagrant::Errors::PushStrategyNotDefined, name: name, - pushes: vagrantfile.pushes + pushes: pushes end end @@ -584,6 +584,13 @@ module Vagrant end end + # The list of pushes defined in this Vagrantfile. + # + # @return [Array] + def pushes + vagrantfile.pushes + end + # This returns a machine with the proper provider for this environment. # The machine named by `name` must be in this environment. # From 3871154a74b8579eb36e765d338c69db5e003654 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Thu, 23 Oct 2014 16:27:21 -0400 Subject: [PATCH 16/83] Ignore options that come back from the plugin for now --- lib/vagrant/environment.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 106d0d9c5..0d6a2a103 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -567,10 +567,7 @@ module Vagrant push_config = vagrantfile.push(name) push_config.each do |strategy, config_blocks| - plugin, options = push_registry.get(strategy) - - # TODO: What do we do with options? - # options + plugin, _ = push_registry.get(strategy) if plugin.nil? raise Vagrant::Errors::PushStrategyNotLoaded, From 413565f96137a995407ab04113fa408b350235b3 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Fri, 24 Oct 2014 15:44:56 -0400 Subject: [PATCH 17/83] Simplify the API for Environment#push The API has a precondition that `name` is not nil --- lib/vagrant/environment.rb | 50 ++++++++++++++------------------------ 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 0d6a2a103..82cab0411 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -542,50 +542,36 @@ module Vagrant # This executes the push with the given name, raising any exceptions that # occur. # - def push(name=nil) + # Precondition: the push is not nil and exists. + def push(name) @logger.info("Getting push: #{name}") - if pushes.nil? || pushes.empty? - raise Vagrant::Errors::PushesNotDefined - end - - if name.nil? - if pushes.length != 1 - raise Vagrant::Errors::PushStrategyNotProvided, - pushes: pushes - end - name = pushes.first - else - if !pushes.include?(name.to_sym) - raise Vagrant::Errors::PushStrategyNotDefined, - name: name, - pushes: pushes - end + name = name.to_sym + + pushes = self.vagrantfile.config.push.__compiled_pushes + if !pushes.key?(name) + raise Vagrant::Errors::PushStrategyNotDefined, + name: name, + pushes: pushes.keys end + strategy, config = pushes[name] push_registry = Vagrant.plugin("2").manager.pushes - - push_config = vagrantfile.push(name) - push_config.each do |strategy, config_blocks| - plugin, _ = push_registry.get(strategy) - - if plugin.nil? - raise Vagrant::Errors::PushStrategyNotLoaded, - name: strategy, - pushes: push_registry.keys - end - - # TODO: This should take a plugin configuration, not a list of config - # blocks, or should it? - plugin.new(self, config_blocks).push + klass, _ = push_registry.get(strategy) + if klass.nil? + raise Vagrant::Errors::PushStrategyNotLoaded, + name: strategy, + pushes: push_registry.keys end + + klass.new(self, config).push end # The list of pushes defined in this Vagrantfile. # # @return [Array] def pushes - vagrantfile.pushes + vagrantfile.config.push.__compiled_pushes.keys end # This returns a machine with the proper provider for this environment. From e5b10aa86bfa7d413cacd2ff630ac137dff4c370 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Fri, 24 Oct 2014 15:45:33 -0400 Subject: [PATCH 18/83] Collect push_configs in the Plugin Manager --- lib/vagrant/plugin/v2/manager.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/vagrant/plugin/v2/manager.rb b/lib/vagrant/plugin/v2/manager.rb index d8f5296db..ce76d1fc1 100644 --- a/lib/vagrant/plugin/v2/manager.rb +++ b/lib/vagrant/plugin/v2/manager.rb @@ -183,6 +183,17 @@ module Vagrant end end + # This returns all the config classes for the various pushes. + # + # @return [Registry] + def push_configs + Registry.new.tap do |result| + @registered.each do |plugin| + result.merge!(plugin.components.configs[:push]) + end + end + end + # This returns all synced folder implementations. # # @return [Registry] From 8e2f18761f7d09eaa8be69e3d1ee4cf1f3f99578 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Fri, 24 Oct 2014 15:45:42 -0400 Subject: [PATCH 19/83] Remove unused methods from vagrantfile.rb --- lib/vagrant/vagrantfile.rb | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/lib/vagrant/vagrantfile.rb b/lib/vagrant/vagrantfile.rb index 7467ffbbc..011d16351 100644 --- a/lib/vagrant/vagrantfile.rb +++ b/lib/vagrant/vagrantfile.rb @@ -247,20 +247,6 @@ module Vagrant nil end - # Returns the list of defined pushes in this Vagrantfile. - # - # @return [Array] - def pushes - @config.push.defined_pushes - end - - # Get the push by the given name. - # - # @return [idk] - def push(name) - @config.push.get_push(name) - end - protected def find_vagrantfile(search_path) From 411c7d6f7514c8657ed9bcd088675c1e7bb26dba Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Fri, 24 Oct 2014 15:46:09 -0400 Subject: [PATCH 20/83] Define finalize! and __compiled_pushes for Push config --- plugins/kernel_v2/config/push.rb | 79 +++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 26 deletions(-) diff --git a/plugins/kernel_v2/config/push.rb b/plugins/kernel_v2/config/push.rb index c5c3a10b0..e8ce5a3ee 100644 --- a/plugins/kernel_v2/config/push.rb +++ b/plugins/kernel_v2/config/push.rb @@ -8,12 +8,59 @@ module VagrantPlugins attr_accessor :name def initialize + @logger = Log4r::Logger.new("vagrant::config::push") + # Internal state - @__defined_pushes = {} - @__finalized = false + @__defined_pushes = {} + @__compiled_pushes = {} + @__finalized = false end def finalize! + @logger.debug("finalizing") + + # Compile all the provider configurations + @__defined_pushes.each do |name, tuples| + # Find the configuration class for this push + config_class = Vagrant.plugin("2").manager.push_configs[name] + config_class ||= Vagrant::Config::V2::DummyConfig + + # Load it up + config = config_class.new + + # Capture the strategy so we can use it later. This will be used in + # the block iteration for merging/overwriting + strategy = (tuples[0] && tuples[0][0]) || name + + begin + tuples.each do |s, b| + # Update the strategy if it has changed, reseting the current + # config object. + if s != strategy + @logger.warn("duplicate strategy defined, overwriting config") + strategy = s + config = config_class.new + end + + # If we don't have any blocks, then ignore it + next if b.nil? + + new_config = config_class.new + b.call(new_config, Vagrant::Config::V2::DummyConfig.new) + config = config.merge(new_config) + end + rescue Exception => e + raise Vagrant::Errors::VagrantfileLoadError, + path: "", + message: e.message + end + + config.finalize! + + # Store it for retrieval later + @__compiled_pushes[name] = [strategy, config] + end + @__finalized = true end @@ -38,8 +85,6 @@ module VagrantPlugins # @param [Hash] options The list of options # def define(name, **options, &block) - validate_options!(options) - name = name.to_sym strategy = options[:strategy] || name @@ -69,30 +114,12 @@ module VagrantPlugins end end - # This returns the list of pushes defined in the Vagrantfile. + # This returns the list of compiled pushes as a hash by name. # - # @return [Array] - def defined_pushes + # @return [Hash>] + def __compiled_pushes raise "Must finalize first!" if !@__finalized - @__defined_pushes.keys - end - - # This returns the compiled push-specific configuration for the given - # provider. - # - # @param [#to_sym] name Name of the push - def get_push(name) - raise "Must finalize first!" if !@__finalized - @__defined_pushes[name.to_sym] - end - - private - - def validate_options!(options) - extra_keys = VALID_OPTIONS - options.keys - if !extra_keys.empty? - raise "Invalid option(s): #{extra_keys.join(", ")}" - end + @__compiled_pushes.dup end end end From 88aa1063278e2c12cc9662f14e75e3c3e3679b06 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Fri, 24 Oct 2014 15:46:16 -0400 Subject: [PATCH 21/83] Fix up i18n missing translation --- templates/locales/en.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/templates/locales/en.yml b/templates/locales/en.yml index fa3c76414..37ed84f07 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -957,19 +957,18 @@ en: The push strategy '%{name}' is not defined in the Vagrantfile. Defined strategy names are: - %{pushes} - - push_strategy_not_defined: |- + %{pushes} + push_strategy_not_loaded: |- There are no push strategies named '%{name}'. Please make sure you spelled it correctly. If you are using an external push strategy, you may need to install a plugin. Loaded push strategies are: - %{pushes} + %{pushes} push_strategy_not_provided: |- The Vagrantfile defines more than one 'push' strategy. Please specify a strategy. Defined strategy names are: - %{pushes} + %{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 From 7f6a4fa3bdb54930c483badcd3d865aa33cd9220 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Fri, 24 Oct 2014 15:46:34 -0400 Subject: [PATCH 22/83] Add tests for plugin manager push_configs --- test/unit/vagrant/plugin/v2/manager_test.rb | 36 +++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/unit/vagrant/plugin/v2/manager_test.rb b/test/unit/vagrant/plugin/v2/manager_test.rb index 45ebd5a0c..3ddbf00e9 100644 --- a/test/unit/vagrant/plugin/v2/manager_test.rb +++ b/test/unit/vagrant/plugin/v2/manager_test.rb @@ -189,6 +189,42 @@ describe Vagrant::Plugin::V2::Manager do expect(instance.provider_configs[:bar]).to eq("bar") end + it "should enumerate registered push classes" do + pA = plugin do |p| + p.push("foo") { "bar" } + end + + pB = plugin do |p| + p.push("bar", foo: "bar") { "baz" } + end + + instance.register(pA) + instance.register(pB) + + expect(instance.pushes.to_hash.length).to eq(2) + expect(instance.pushes[:foo]).to eq(["bar", nil]) + expect(instance.pushes[:bar]).to eq(["baz", { foo: "bar" }]) + end + + it "provides the collection of registered push configs" do + pA = plugin do |p| + p.config("foo", :push) { "foo" } + end + + pB = plugin do |p| + p.config("bar", :push) { "bar" } + p.config("baz") { "baz" } + end + + instance.register(pA) + instance.register(pB) + + expect(instance.push_configs.to_hash.length).to eq(2) + expect(instance.push_configs[:foo]).to eq("foo") + expect(instance.push_configs[:bar]).to eq("bar") + end + + it "should enumerate all registered synced folder implementations" do pA = plugin do |p| p.synced_folder("foo") { "bar" } From f3c35855f06e2cb8ebe05a5dc23f0fc54627f69f Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Fri, 24 Oct 2014 15:46:44 -0400 Subject: [PATCH 23/83] Add a newline because #ocd --- test/unit/vagrant/plugin/v2/plugin_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/unit/vagrant/plugin/v2/plugin_test.rb b/test/unit/vagrant/plugin/v2/plugin_test.rb index 442462805..76ad40993 100644 --- a/test/unit/vagrant/plugin/v2/plugin_test.rb +++ b/test/unit/vagrant/plugin/v2/plugin_test.rb @@ -357,6 +357,7 @@ describe Vagrant::Plugin::V2::Plugin do }.to raise_error(StandardError) end end + describe "synced folders" do it "should register implementations" do plugin = Class.new(described_class) do From 111a43552ec082926b0c0b23fc578c57d59c254a Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Fri, 24 Oct 2014 15:46:57 -0400 Subject: [PATCH 24/83] Add tests for Environment#pushes and #Enviroment#push --- test/unit/vagrant/environment_test.rb | 70 +++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/test/unit/vagrant/environment_test.rb b/test/unit/vagrant/environment_test.rb index 2bb52e939..02effd7db 100644 --- a/test/unit/vagrant/environment_test.rb +++ b/test/unit/vagrant/environment_test.rb @@ -968,6 +968,76 @@ VF end end + describe "#pushes" do + it "returns the pushes from the Vagrantfile config" do + environment = isolated_environment do |env| + env.vagrantfile(<<-VF.gsub(/^ {10}/, '')) + Vagrant.configure("2") do |config| + config.push.define "noop" + end + VF + end + + env = environment.create_vagrant_env + expect(env.pushes).to eq([:noop]) + end + end + + describe "#push" do + let(:push_class) do + Class.new(Vagrant.plugin("2", :push)) do + def self.pushed? + !!class_variable_get(:@@pushed) + end + + def push + !!self.class.class_variable_set(:@@pushed, true) + end + end + end + + it "raises an exception when the push does not exist" do + expect { instance.push("lolwatbacon") } + .to raise_error(Vagrant::Errors::PushStrategyNotDefined) + end + + it "raises an exception if the strategy does not exist" do + environment = isolated_environment do |env| + env.vagrantfile(<<-VF.gsub(/^ {10}/, '')) + Vagrant.configure("2") do |config| + config.push.define "lolwatbacon" + end + VF + end + + env = environment.create_vagrant_env + expect { env.push("lolwatbacon") } + .to raise_error(Vagrant::Errors::PushStrategyNotLoaded) + end + + it "executes the push action" do + register_plugin("2") do |plugin| + plugin.name "foo" + + plugin.push(:foo) do + push_class + end + end + + environment = isolated_environment do |env| + env.vagrantfile(<<-VF.gsub(/^ {10}/, '')) + Vagrant.configure("2") do |config| + config.push.define "foo" + end + VF + end + + env = environment.create_vagrant_env + env.push("foo") + expect(push_class.pushed?).to be_true + end + end + describe "#hook" do it "should call the action runner with the proper hook" do hook_name = :foo From 41ac448ba8540110db90838a902d8c8b6f69675a Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Fri, 24 Oct 2014 15:47:07 -0400 Subject: [PATCH 25/83] Add tests for Push config merging and finalizing --- .../plugins/kernel_v2/config/push_test.rb | 300 ++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 test/unit/plugins/kernel_v2/config/push_test.rb diff --git a/test/unit/plugins/kernel_v2/config/push_test.rb b/test/unit/plugins/kernel_v2/config/push_test.rb new file mode 100644 index 000000000..28430d171 --- /dev/null +++ b/test/unit/plugins/kernel_v2/config/push_test.rb @@ -0,0 +1,300 @@ +require File.expand_path("../../../../base", __FILE__) + +require Vagrant.source_root.join("plugins/kernel_v2/config/push") + +describe VagrantPlugins::Kernel_V2::PushConfig do + include_context "unit" + + subject { described_class.new } + + describe "#define" do + let(:pushes) { subject.instance_variable_get(:@__defined_pushes) } + + it "pushes the strategy and block onto the defined pushes array" do + subject.define("foo") { "bar" } + subject.define("foo") { "zip" } + subject.define("foo") { "zap" } + + expect(pushes.size).to eq(1) + expect(pushes[:foo].size).to eq(3) + expect(pushes[:foo][0]).to be_a(Array) + expect(pushes[:foo][0][0]).to eq(:foo) + expect(pushes[:foo][0][1]).to be_a(Proc) + end + + context "when no strategy is given" do + it "defaults to the name" do + subject.define("foo") { "bar" } + + expect(pushes.size).to eq(1) + expect(pushes[:foo].size).to eq(1) + expect(pushes[:foo][0]).to be_a(Array) + expect(pushes[:foo][0][0]).to eq(:foo) + expect(pushes[:foo][0][1]).to be_a(Proc) + end + end + + context "when a strategy is given" do + it "uses the strategy" do + subject.define("foo", strategy: "bacon") { "bar" } + + expect(pushes.size).to eq(1) + expect(pushes[:foo].size).to eq(1) + expect(pushes[:foo][0]).to be_a(Array) + expect(pushes[:foo][0][0]).to eq(:bacon) + expect(pushes[:foo][0][1]).to be_a(Proc) + end + end + end + + describe "#merge" do + it "appends defined pushes" do + a = described_class.new.tap do |i| + i.define("foo") { "bar" } + i.define("bar") { "bar" } + end + b = described_class.new.tap do |i| + i.define("foo") { "zip" } + end + + result = a.merge(b) + pushes = result.instance_variable_get(:@__defined_pushes) + + expect(pushes[:foo]).to be_a(Array) + expect(pushes[:foo].size).to eq(2) + + expect(pushes[:bar]).to be_a(Array) + expect(pushes[:bar].size).to eq(1) + end + end + + describe "#__compiled_pushes" do + it "raises an exception if not finalized" do + subject.instance_variable_set(:@__finalized, false) + expect { subject.__compiled_pushes }.to raise_error + end + + it "returns a copy of the compiled pushes" do + pushes = { foo: "bar" } + subject.instance_variable_set(:@__finalized, true) + subject.instance_variable_set(:@__compiled_pushes, pushes) + + expect(subject.__compiled_pushes).to_not be(pushes) + expect(subject.__compiled_pushes).to eq(pushes) + end + end + + describe "#finalize!" do + let(:pushes) { a.merge(b).tap { |r| r.finalize! }.__compiled_pushes } + let(:key) { pushes[:foo][0] } + let(:config) { pushes[:foo][1] } + let(:unset) { Vagrant.plugin("2", :config).const_get(:UNSET_VALUE) } + + before do + register_plugin("2") do |plugin| + plugin.name "foo" + + plugin.push(:foo) do + Class.new(Vagrant.plugin("2", :push)) + end + + plugin.config(:foo, :push) do + Class.new(Vagrant.plugin("2", :config)) do + attr_accessor :bar + attr_accessor :zip + + def initialize + @bar = self.class.const_get(:UNSET_VALUE) + @zip = self.class.const_get(:UNSET_VALUE) + end + end + end + end + end + + context "with the same name but different strategy" do + context "with no block" do + let(:a) do + described_class.new.tap do |i| + i.define("foo", strategy: "bar") + end + end + + let(:b) do + described_class.new.tap do |i| + i.define("foo", strategy: "zip") + end + end + + it "chooses the last config" do + expect(key).to eq(:zip) + expect(config.bar).to be(unset) + expect(config.zip).to be(unset) + end + end + + context "with a block" do + let(:a) do + described_class.new.tap do |i| + i.define("foo", strategy: "bar") do |p| + p.bar = "a" + end + end + end + + let(:b) do + described_class.new.tap do |i| + i.define("foo", strategy: "zip") do |p| + p.zip = "b" + end + end + end + + it "chooses the last config" do + expect(key).to eq(:zip) + expect(config.bar).to eq(unset) + expect(config.zip).to eq("b") + end + end + + context "with a block, then no block" do + let(:a) do + described_class.new.tap do |i| + i.define("foo", strategy: "bar") do |p| + p.bar, p.zip = "a", "a" + end + end + end + + let(:b) do + described_class.new.tap do |i| + i.define("foo", strategy: "zip") + end + end + + it "chooses the last config" do + expect(key).to eq(:zip) + expect(config.bar).to be(unset) + expect(config.zip).to be(unset) + end + end + + context "with no block, then a block" do + let(:a) do + described_class.new.tap do |i| + i.define("foo", strategy: "bar") + end + end + + let(:b) do + described_class.new.tap do |i| + i.define("foo", strategy: "zip") do |p| + p.bar, p.zip = "b", "b" + end + end + end + + it "chooses the last config" do + expect(key).to eq(:zip) + expect(config.bar).to eq("b") + expect(config.zip).to eq("b") + end + end + end + + context "with the same name twice" do + context "with no block" do + let(:a) do + described_class.new.tap do |i| + i.define("foo") + end + end + + let(:b) do + described_class.new.tap do |i| + i.define("foo") + end + end + + it "merges the configs" do + expect(key).to eq(:foo) + expect(config.bar).to be(unset) + expect(config.zip).to be(unset) + end + end + + context "with a block" do + let(:a) do + described_class.new.tap do |i| + i.define("foo") do |p| + p.bar = "a" + end + end + end + + let(:b) do + described_class.new.tap do |i| + i.define("foo") do |p| + p.zip = "b" + end + end + end + + it "merges the configs" do + expect(key).to eq(:foo) + expect(config.bar).to eq("a") + expect(config.zip).to eq("b") + end + end + + context "with a block, then no block" do + let(:a) do + described_class.new.tap do |i| + i.define("foo") do |p| + p.bar = "a" + end + end + end + + let(:b) do + described_class.new.tap do |i| + i.define("foo") + end + end + + it "merges the configs" do + expect(key).to eq(:foo) + expect(config.bar).to eq("a") + expect(config.zip).to be(unset) + end + end + + context "with no block, then a block" do + let(:a) do + described_class.new.tap do |i| + i.define("foo", strategy: "bar") + end + end + + let(:b) do + described_class.new.tap do |i| + i.define("foo", strategy: "zip") do |p| + p.zip = "b" + end + end + end + + it "merges the configs" do + expect(key).to eq(:zip) + expect(config.bar).to eq(unset) + expect(config.zip).to eq("b") + end + end + end + + it "sets @__finalized to true" do + subject.finalize! + expect(subject.instance_variable_get(:@__finalized)).to be(true) + end + end +end From 35b7e28011ff5594d769b36d66a91b2acaa564fc Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Fri, 24 Oct 2014 19:49:10 -0400 Subject: [PATCH 26/83] Do not use UNSET_VALUE in plugin (it uses components) --- lib/vagrant/plugin/v2/plugin.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vagrant/plugin/v2/plugin.rb b/lib/vagrant/plugin/v2/plugin.rb index bcae23eaa..a39b3cf10 100644 --- a/lib/vagrant/plugin/v2/plugin.rb +++ b/lib/vagrant/plugin/v2/plugin.rb @@ -225,7 +225,7 @@ module Vagrant # # @param [String] name Name of the push. # @param [Hash] options List of options for the push. - def self.push(name=UNSET_VALUE, options=nil, &block) + def self.push(name, options=nil, &block) components.pushes.register(name.to_sym) do [block.call, options] end From 1121e96cf7db961bf19294263154527b5eb9d23e Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Fri, 24 Oct 2014 19:49:58 -0400 Subject: [PATCH 27/83] Remove TODO comment about duplicate code --- plugins/commands/push/command.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/commands/push/command.rb b/plugins/commands/push/command.rb index ec7324b55..dc02f7ba7 100644 --- a/plugins/commands/push/command.rb +++ b/plugins/commands/push/command.rb @@ -20,8 +20,6 @@ module VagrantPlugins name = argv[0] pushes = @env.pushes - # TODO: this logic is 100% duplicated in Enviroment#push - should we - # just not validate here? validate_pushes!(pushes, name) @logger.debug("'push' environment with strategy: `#{name}'") From 9af7675bd3a1828f606c31b5717885c6a8e1c752 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Fri, 24 Oct 2014 19:50:52 -0400 Subject: [PATCH 28/83] Use a more readable version for setter --- plugins/kernel_v2/config/push.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/kernel_v2/config/push.rb b/plugins/kernel_v2/config/push.rb index e8ce5a3ee..e0137b29d 100644 --- a/plugins/kernel_v2/config/push.rb +++ b/plugins/kernel_v2/config/push.rb @@ -30,7 +30,8 @@ module VagrantPlugins # Capture the strategy so we can use it later. This will be used in # the block iteration for merging/overwriting - strategy = (tuples[0] && tuples[0][0]) || name + strategy = name + strategy = tuples[0][0] if tuples[0] begin tuples.each do |s, b| From e7b0661a93a9718604cd0da0e8e07db01763c65f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 28 Oct 2014 20:54:16 -0700 Subject: [PATCH 29/83] pushes/harmony: boilerplate, config --- plugins/pushes/harmony/config.rb | 73 ++++++++++++++++ plugins/pushes/harmony/plugin.rb | 22 +++++ plugins/pushes/harmony/push.rb | 9 ++ .../plugins/pushes/harmony/config_test.rb | 84 +++++++++++++++++++ 4 files changed, 188 insertions(+) create mode 100644 plugins/pushes/harmony/config.rb create mode 100644 plugins/pushes/harmony/plugin.rb create mode 100644 plugins/pushes/harmony/push.rb create mode 100644 test/unit/plugins/pushes/harmony/config_test.rb diff --git a/plugins/pushes/harmony/config.rb b/plugins/pushes/harmony/config.rb new file mode 100644 index 000000000..5053c328e --- /dev/null +++ b/plugins/pushes/harmony/config.rb @@ -0,0 +1,73 @@ +module VagrantPlugins + module HarmonyPush + class Config < Vagrant.plugin("2", :config) + # The name of the application to push to. This will be created (with + # user confirmation) if it doesn't already exist. + # + # @return [String] + attr_accessor :app + + # The base directory with file contents to upload. By default this + # is the same directory as the Vagrantfile, but you can specify this + # if you have a `src` folder or `bin` folder or some other folder + # you want to upload. + # + # @return [String] + attr_accessor :dir + + # Lists of files to include/exclude in what is uploaded. Exclude is + # always the last run filter, so if a file is matched in both include + # and exclude, it will be excluded. + # + # The value of the array elements should be a simple file glob relative + # to the directory being packaged. + # + # @return [Array] + attr_accessor :include + attr_accessor :exclude + + # If set to true, Vagrant will automatically use VCS data to determine + # the files to upload. As a caveat: uncommitted changes will not be + # deployed. + # + # @return [Boolean] + attr_accessor :vcs + + def initialize + @app = UNSET_VALUE + @dir = UNSET_VALUE + @vcs = UNSET_VALUE + @include = [] + @exclude = [] + end + + def merge(other) + super.tap do |result| + inc = self.include.dup + inc.concat(other.include) + result.include = inc + + exc = self.exclude.dup + exc.concat(other.exclude) + result.exclude = exc + end + end + + def finalize! + @app = nil if @app == UNSET_VALUE + @dir = "." if @dir == UNSET_VALUE + @vcs = true if @vcs == UNSET_VALUE + end + + def validate(machine) + errors = _detected_errors + + if @app == nil || @app == "" + errors << I18n.t("push_harmony.errors.config.app_required") + end + + { "Harmony push" => errors } + end + end + end +end diff --git a/plugins/pushes/harmony/plugin.rb b/plugins/pushes/harmony/plugin.rb new file mode 100644 index 000000000..a0c013f82 --- /dev/null +++ b/plugins/pushes/harmony/plugin.rb @@ -0,0 +1,22 @@ +require "vagrant" + +module VagrantPlugins + module HarmonyPush + class Plugin < Vagrant.plugin("2") + name "harmony" + description <<-DESC + Deploy using HashiCorp's Harmony service. + DESC + + config(:harmony, :push) do + require File.expand_path("../config", __FILE__) + Config + end + + push(:harmony) do + require File.expand_path("../push", __FILE__) + Push + end + end + end +end diff --git a/plugins/pushes/harmony/push.rb b/plugins/pushes/harmony/push.rb new file mode 100644 index 000000000..20f1c355e --- /dev/null +++ b/plugins/pushes/harmony/push.rb @@ -0,0 +1,9 @@ +module VagrantPlugins + module HarmonyPush + class Push < Vagrant.plugin("2", :push) + def push + puts "pushed" + end + end + end +end diff --git a/test/unit/plugins/pushes/harmony/config_test.rb b/test/unit/plugins/pushes/harmony/config_test.rb new file mode 100644 index 000000000..de5ce1c33 --- /dev/null +++ b/test/unit/plugins/pushes/harmony/config_test.rb @@ -0,0 +1,84 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/pushes/harmony/config") + +describe VagrantPlugins::HarmonyPush::Config do + include_context "unit" + + let(:machine) { double("machine") } + + # For testing merging + let(:one) { described_class.new } + let(:two) { described_class.new } + + def assert_invalid + errors = subject.validate(machine) + if !errors.values.any? { |v| !v.empty? } + raise "No errors: #{errors.inspect}" + end + end + + def assert_valid + errors = subject.validate(machine) + if !errors.values.all? { |v| v.empty? } + raise "Errors: #{errors.inspect}" + end + end + + def valid_defaults + end + + describe "defaults" do + before { subject.finalize! } + + its(:app) { should be_nil } + its(:dir) { should eq(".") } + its(:exclude) { should be_empty } + its(:include) { should be_empty } + its(:vcs) { should be_true } + end + + describe "app" do + before do + valid_defaults + end + + it "is invalid if not set" do + subject.app = "" + subject.finalize! + assert_invalid + end + + it "is valid if set" do + subject.app = "foo/bar" + subject.finalize! + assert_valid + end + end + + describe "exclude" do + context "merge" do + subject { one.merge(two) } + + it "appends" do + one.exclude = ["foo"] + two.exclude = ["bar"] + + expect(subject.exclude).to eq(["foo", "bar"]) + end + end + end + + describe "include" do + context "merge" do + subject { one.merge(two) } + + it "appends" do + one.include = ["foo"] + two.include = ["bar"] + + expect(subject.include).to eq(["foo", "bar"]) + end + end + end +end From 168715ad7d2bd8a703b1ddb99c0fa06c706930f3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 29 Oct 2014 00:05:31 -0700 Subject: [PATCH 30/83] push/harmony: ability to set uploader path --- plugins/pushes/harmony/config.rb | 10 ++++++++++ test/unit/plugins/pushes/harmony/config_test.rb | 1 + 2 files changed, 11 insertions(+) diff --git a/plugins/pushes/harmony/config.rb b/plugins/pushes/harmony/config.rb index 5053c328e..d9890079e 100644 --- a/plugins/pushes/harmony/config.rb +++ b/plugins/pushes/harmony/config.rb @@ -33,12 +33,21 @@ module VagrantPlugins # @return [Boolean] attr_accessor :vcs + # The path to the uploader binary to shell out to. This usually + # is only set for debugging/development. If not set, the uploader + # will be looked for within the Vagrant installer dir followed by + # the PATH. + # + # @return [String] + attr_accessor :uploader_path + def initialize @app = UNSET_VALUE @dir = UNSET_VALUE @vcs = UNSET_VALUE @include = [] @exclude = [] + @uploader_path = UNSET_VALUE end def merge(other) @@ -56,6 +65,7 @@ module VagrantPlugins def finalize! @app = nil if @app == UNSET_VALUE @dir = "." if @dir == UNSET_VALUE + @uploader_path = nil if @uploader_path == UNSET_VALUE @vcs = true if @vcs == UNSET_VALUE end diff --git a/test/unit/plugins/pushes/harmony/config_test.rb b/test/unit/plugins/pushes/harmony/config_test.rb index de5ce1c33..0806156e3 100644 --- a/test/unit/plugins/pushes/harmony/config_test.rb +++ b/test/unit/plugins/pushes/harmony/config_test.rb @@ -35,6 +35,7 @@ describe VagrantPlugins::HarmonyPush::Config do its(:dir) { should eq(".") } its(:exclude) { should be_empty } its(:include) { should be_empty } + its(:uploader_path) { should be_nil } its(:vcs) { should be_true } end From efffc5f2f7a1b9374ff980c6bb30b0a47d403d9a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 29 Oct 2014 09:39:26 -0700 Subject: [PATCH 31/83] push/harmony: basic push implementation --- plugins/pushes/harmony/errors.rb | 13 +++ plugins/pushes/harmony/plugin.rb | 2 + plugins/pushes/harmony/push.rb | 46 +++++++- test/unit/plugins/pushes/harmony/push_test.rb | 103 ++++++++++++++++++ 4 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 plugins/pushes/harmony/errors.rb create mode 100644 test/unit/plugins/pushes/harmony/push_test.rb diff --git a/plugins/pushes/harmony/errors.rb b/plugins/pushes/harmony/errors.rb new file mode 100644 index 000000000..9eb590ff3 --- /dev/null +++ b/plugins/pushes/harmony/errors.rb @@ -0,0 +1,13 @@ +module VagrantPlugins + module HarmonyPush + module Errors + class Error < Vagrant::Errors::VagrantError + error_namespace("harmony_push.errors") + end + + class UploaderNotFound < Error + error_key(:uploader_error) + end + end + end +end diff --git a/plugins/pushes/harmony/plugin.rb b/plugins/pushes/harmony/plugin.rb index a0c013f82..bcf662c01 100644 --- a/plugins/pushes/harmony/plugin.rb +++ b/plugins/pushes/harmony/plugin.rb @@ -2,6 +2,8 @@ require "vagrant" module VagrantPlugins module HarmonyPush + autoload :Errors, File.expand_path("../errors", __FILE__) + class Plugin < Vagrant.plugin("2") name "harmony" description <<-DESC diff --git a/plugins/pushes/harmony/push.rb b/plugins/pushes/harmony/push.rb index 20f1c355e..5a3907249 100644 --- a/plugins/pushes/harmony/push.rb +++ b/plugins/pushes/harmony/push.rb @@ -1,8 +1,52 @@ +require "vagrant/util/safe_exec" +require "vagrant/util/subprocess" +require "vagrant/util/which" + module VagrantPlugins module HarmonyPush class Push < Vagrant.plugin("2", :push) + UPLOADER_BIN = "harmony-upload" + def push - puts "pushed" + uploader = self.uploader_path + + # If we didn't find the uploader binary it is a critical error + raise Errors::UploaderNotFound if !uploader + + # We found it. Build up the command and the args. + execute(uploader) + return 0 + end + + # Executes the uploader with the proper flags based on the configuration. + # This function shouldn't return since it will exec, but might return + # if we're on a system that doesn't support exec, so handle that properly. + def execute(uploader) + cmd = [] + cmd << "-vcs" if @config.vcs + cmd += @config.include.map { |v| ["-include", v] } if !@config.include.empty? + cmd += @config.exclude.map { |v| ["-exclude", v] } if !@config.exclude.empty? + cmd << @config.app + cmd << @config.dir + Vagrant::Util::SafeExec.exec(uploader, *cmd.flatten) + end + + # This returns the path to the uploader binary, or nil if it can't + # be found. + # + # @return [String] + def uploader_path + # Determine the uploader path + uploader = @config.uploader_path + if uploader + return uploader + end + + if Vagrant.in_installer? + # TODO: look up uploader in embedded dir + else + return Vagrant::Util::Which.which(UPLOADER_BIN) + end end end end diff --git a/test/unit/plugins/pushes/harmony/push_test.rb b/test/unit/plugins/pushes/harmony/push_test.rb new file mode 100644 index 000000000..9b386370c --- /dev/null +++ b/test/unit/plugins/pushes/harmony/push_test.rb @@ -0,0 +1,103 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/pushes/harmony/config") +require Vagrant.source_root.join("plugins/pushes/harmony/push") + +describe VagrantPlugins::HarmonyPush::Push do + include_context "unit" + + let(:config) do + VagrantPlugins::HarmonyPush::Config.new.tap do |c| + c.finalize! + end + end + + let(:machine) { double("machine") } + + subject { described_class.new(machine, config) } + + before do + # Stub this right away to avoid real execs + allow(Vagrant::Util::SafeExec).to receive(:exec) + end + + describe "#push" do + it "pushes with the uploader" do + allow(subject).to receive(:uploader_path).and_return("foo") + + expect(subject).to receive(:execute).with("foo") + + subject.push + end + + it "raises an exception if the uploader couldn't be found" do + expect(subject).to receive(:uploader_path).and_return(nil) + + expect { subject.push }.to raise_error( + VagrantPlugins::HarmonyPush::Errors::UploaderNotFound) + end + end + + describe "#execute" do + let(:app) { "foo/bar" } + + before do + config.app = app + end + + it "sends the basic flags" do + expect(Vagrant::Util::SafeExec).to receive(:exec). + with("foo", "-vcs", app, ".") + + subject.execute("foo") + end + + it "doesn't send VCS if disabled" do + expect(Vagrant::Util::SafeExec).to receive(:exec). + with("foo", app, ".") + + config.vcs = false + subject.execute("foo") + end + + it "sends includes" do + expect(Vagrant::Util::SafeExec).to receive(:exec). + with("foo", "-vcs", "-include", "foo", "-include", "bar", app, ".") + + config.include = ["foo", "bar"] + subject.execute("foo") + end + + it "sends excludes" do + expect(Vagrant::Util::SafeExec).to receive(:exec). + with("foo", "-vcs", "-exclude", "foo", "-exclude", "bar", app, ".") + + config.exclude = ["foo", "bar"] + subject.execute("foo") + end + end + + describe "#uploader_path" do + it "should return the configured path if set" do + config.uploader_path = "foo" + expect(subject.uploader_path).to eq("foo") + end + + it "should look up the uploader via PATH if not set" do + allow(Vagrant).to receive(:in_installer?).and_return(false) + + expect(Vagrant::Util::Which).to receive(:which). + with(described_class.const_get(:UPLOADER_BIN)). + and_return("bar") + + expect(subject.uploader_path).to eq("bar") + end + + it "should return nil if its not found anywhere" do + allow(Vagrant).to receive(:in_installer?).and_return(false) + allow(Vagrant::Util::Which).to receive(:which).and_return(nil) + + expect(subject.uploader_path).to be_nil + end + end +end From f3f4f4aeb62c6893d3932b0c68f1e4cabe3461a5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 29 Oct 2014 09:41:47 -0700 Subject: [PATCH 32/83] pushes/harmony: stub I18n --- plugins/pushes/harmony/plugin.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/plugins/pushes/harmony/plugin.rb b/plugins/pushes/harmony/plugin.rb index bcf662c01..51630cfbf 100644 --- a/plugins/pushes/harmony/plugin.rb +++ b/plugins/pushes/harmony/plugin.rb @@ -12,13 +12,25 @@ module VagrantPlugins config(:harmony, :push) do require File.expand_path("../config", __FILE__) + init! Config end push(:harmony) do require File.expand_path("../push", __FILE__) + init! Push end + + protected + + def self.init! + return if defined?(@_init) + I18n.load_path << File.expand_path( + "templates/locales/TODO.yml", Vagrant.source_root) + I18n.reload! + @_init = true + end end end end From 170546088080a8f68ba5d9ebcf3e733bef08a14e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 29 Oct 2014 09:48:18 -0700 Subject: [PATCH 33/83] pushes/harmony: expand dir relative to Vagrantfile root path --- plugins/pushes/harmony/push.rb | 2 +- test/unit/plugins/pushes/harmony/push_test.rb | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/plugins/pushes/harmony/push.rb b/plugins/pushes/harmony/push.rb index 5a3907249..f3906d834 100644 --- a/plugins/pushes/harmony/push.rb +++ b/plugins/pushes/harmony/push.rb @@ -27,7 +27,7 @@ module VagrantPlugins cmd += @config.include.map { |v| ["-include", v] } if !@config.include.empty? cmd += @config.exclude.map { |v| ["-exclude", v] } if !@config.exclude.empty? cmd << @config.app - cmd << @config.dir + cmd << File.expand_path(@config.dir, @environment.root_path) Vagrant::Util::SafeExec.exec(uploader, *cmd.flatten) end diff --git a/test/unit/plugins/pushes/harmony/push_test.rb b/test/unit/plugins/pushes/harmony/push_test.rb index 9b386370c..bf365cc24 100644 --- a/test/unit/plugins/pushes/harmony/push_test.rb +++ b/test/unit/plugins/pushes/harmony/push_test.rb @@ -12,13 +12,16 @@ describe VagrantPlugins::HarmonyPush::Push do end end - let(:machine) { double("machine") } + let(:environment) { double("environment") } - subject { described_class.new(machine, config) } + subject { described_class.new(environment, config) } before do # Stub this right away to avoid real execs allow(Vagrant::Util::SafeExec).to receive(:exec) + + allow(environment).to receive(:root_path).and_return( + File.expand_path("../", __FILE__)) end describe "#push" do @@ -47,14 +50,14 @@ describe VagrantPlugins::HarmonyPush::Push do it "sends the basic flags" do expect(Vagrant::Util::SafeExec).to receive(:exec). - with("foo", "-vcs", app, ".") + with("foo", "-vcs", app, environment.root_path.to_s) subject.execute("foo") end it "doesn't send VCS if disabled" do expect(Vagrant::Util::SafeExec).to receive(:exec). - with("foo", app, ".") + with("foo", app, environment.root_path.to_s) config.vcs = false subject.execute("foo") @@ -62,7 +65,8 @@ describe VagrantPlugins::HarmonyPush::Push do it "sends includes" do expect(Vagrant::Util::SafeExec).to receive(:exec). - with("foo", "-vcs", "-include", "foo", "-include", "bar", app, ".") + with("foo", "-vcs", "-include", "foo", "-include", + "bar", app, environment.root_path.to_s) config.include = ["foo", "bar"] subject.execute("foo") @@ -70,7 +74,8 @@ describe VagrantPlugins::HarmonyPush::Push do it "sends excludes" do expect(Vagrant::Util::SafeExec).to receive(:exec). - with("foo", "-vcs", "-exclude", "foo", "-exclude", "bar", app, ".") + with("foo", "-vcs", "-exclude", "foo", "-exclude", + "bar", app, environment.root_path.to_s) config.exclude = ["foo", "bar"] subject.execute("foo") From fefaa8da71dddc473e2c4ec45b670a37afa58fc5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 29 Oct 2014 09:49:37 -0700 Subject: [PATCH 34/83] pushes/harmony: use to_s.strip.empty? to check if app is set --- plugins/pushes/harmony/config.rb | 2 +- test/unit/plugins/pushes/harmony/config_test.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/pushes/harmony/config.rb b/plugins/pushes/harmony/config.rb index d9890079e..6698cadec 100644 --- a/plugins/pushes/harmony/config.rb +++ b/plugins/pushes/harmony/config.rb @@ -72,7 +72,7 @@ module VagrantPlugins def validate(machine) errors = _detected_errors - if @app == nil || @app == "" + if @app.to_s.strip.empty? errors << I18n.t("push_harmony.errors.config.app_required") end diff --git a/test/unit/plugins/pushes/harmony/config_test.rb b/test/unit/plugins/pushes/harmony/config_test.rb index 0806156e3..17cd4c1fe 100644 --- a/test/unit/plugins/pushes/harmony/config_test.rb +++ b/test/unit/plugins/pushes/harmony/config_test.rb @@ -50,6 +50,12 @@ describe VagrantPlugins::HarmonyPush::Config do assert_invalid end + it "is invalid if blank" do + subject.app = " " + subject.finalize! + assert_invalid + end + it "is valid if set" do subject.app = "foo/bar" subject.finalize! From 81f748347ecfeaa83fa6ed4ae12c225ddc37eb1d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 29 Oct 2014 09:52:06 -0700 Subject: [PATCH 35/83] pushes/harmony: fixes from @sethvargo --- plugins/pushes/harmony/plugin.rb | 4 ++-- plugins/pushes/harmony/push.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/pushes/harmony/plugin.rb b/plugins/pushes/harmony/plugin.rb index 51630cfbf..331da4b32 100644 --- a/plugins/pushes/harmony/plugin.rb +++ b/plugins/pushes/harmony/plugin.rb @@ -11,13 +11,13 @@ module VagrantPlugins DESC config(:harmony, :push) do - require File.expand_path("../config", __FILE__) + require_relative "config" init! Config end push(:harmony) do - require File.expand_path("../push", __FILE__) + require_relative "push" init! Push end diff --git a/plugins/pushes/harmony/push.rb b/plugins/pushes/harmony/push.rb index f3906d834..660d3b0fb 100644 --- a/plugins/pushes/harmony/push.rb +++ b/plugins/pushes/harmony/push.rb @@ -5,7 +5,7 @@ require "vagrant/util/which" module VagrantPlugins module HarmonyPush class Push < Vagrant.plugin("2", :push) - UPLOADER_BIN = "harmony-upload" + UPLOADER_BIN = "harmony-upload".freeze def push uploader = self.uploader_path From 913dafd3aa48ae9fd8ad3f47636266a4373dd007 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 29 Oct 2014 09:55:43 -0700 Subject: [PATCH 36/83] pushes/harmony: remove unneceessary if --- plugins/pushes/harmony/push.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/pushes/harmony/push.rb b/plugins/pushes/harmony/push.rb index 660d3b0fb..9fd926fee 100644 --- a/plugins/pushes/harmony/push.rb +++ b/plugins/pushes/harmony/push.rb @@ -24,8 +24,8 @@ module VagrantPlugins def execute(uploader) cmd = [] cmd << "-vcs" if @config.vcs - cmd += @config.include.map { |v| ["-include", v] } if !@config.include.empty? - cmd += @config.exclude.map { |v| ["-exclude", v] } if !@config.exclude.empty? + cmd += @config.include.map { |v| ["-include", v] } + cmd += @config.exclude.map { |v| ["-exclude", v] } cmd << @config.app cmd << File.expand_path(@config.dir, @environment.root_path) Vagrant::Util::SafeExec.exec(uploader, *cmd.flatten) From 1ac68808e0dce66ee54543eb72afd29db08bf508 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 11 Nov 2014 18:35:20 -0500 Subject: [PATCH 37/83] Add net-sftp as a dep --- vagrant.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/vagrant.gemspec b/vagrant.gemspec index 1981493ff..981ce3ed3 100644 --- a/vagrant.gemspec +++ b/vagrant.gemspec @@ -23,6 +23,7 @@ Gem::Specification.new do |s| s.add_dependency "hashicorp-checkpoint", "~> 0.1.1" s.add_dependency "log4r", "~> 1.1.9", "< 1.1.11" s.add_dependency "net-ssh", ">= 2.6.6", "< 2.10.0" + s.add_dependency "net-sftp", "~> 2.1" s.add_dependency "net-scp", "~> 1.1.0" s.add_dependency "rb-kqueue", "~> 0.2.0" s.add_dependency "wdm", "~> 0.1.0" From f85d96b4253e3c0939583050153a9eb00a5cd42b Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 11 Nov 2014 18:35:27 -0500 Subject: [PATCH 38/83] Add fake_ftp as a development dep --- vagrant.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/vagrant.gemspec b/vagrant.gemspec index 981ce3ed3..23931940b 100644 --- a/vagrant.gemspec +++ b/vagrant.gemspec @@ -34,6 +34,7 @@ Gem::Specification.new do |s| s.add_development_dependency "rake" s.add_development_dependency "rspec", "~> 2.14.0" + s.add_development_dependency "fake_ftp", "~> 0.1" # The following block of code determines the files that should be included # in the gem. It does this by reading all the files in the directory where From b90253ea8ce6dfab21953934193ae4ac136ddc65 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 11 Nov 2014 18:35:58 -0500 Subject: [PATCH 39/83] Create ftp push config --- plugins/pushes/ftp/config.rb | 128 +++++++++++++++ plugins/pushes/ftp/locales/en.yml | 9 ++ test/unit/plugins/pushes/ftp/config_test.rb | 171 ++++++++++++++++++++ 3 files changed, 308 insertions(+) create mode 100644 plugins/pushes/ftp/config.rb create mode 100644 plugins/pushes/ftp/locales/en.yml create mode 100644 test/unit/plugins/pushes/ftp/config_test.rb diff --git a/plugins/pushes/ftp/config.rb b/plugins/pushes/ftp/config.rb new file mode 100644 index 000000000..7ee1961a4 --- /dev/null +++ b/plugins/pushes/ftp/config.rb @@ -0,0 +1,128 @@ +module VagrantPlugins + module FTPPush + class Config < Vagrant.plugin("2", :config) + # The (S)FTP host to use. + # @return [String] + attr_accessor :host + + # The username to use for authentication with the (S)FTP server. + # @return [String] + attr_accessor :username + + # The password to use for authentication with the (S)FTP server. + # @return [String] + attr_accessor :password + + # Use passive FTP (default is true). + # @return [true, false] + attr_accessor :passive + + # Use secure (SFTP) (default is false). + # @return [true, false] + attr_accessor :secure + + # The root destination on the target system to sync the files (default is + # /). + # @return [String] + attr_accessor :destination + + # Lists of files to include/exclude in what is uploaded. Exclude is + # always the last run filter, so if a file is matched in both include + # and exclude, it will be excluded. + # + # The value of the array elements should be a simple file glob relative + # to the directory being packaged. + # @return [Array] + attr_accessor :includes + attr_accessor :excludes + + # The base directory with file contents to upload. By default this + # is the same directory as the Vagrantfile, but you can specify this + # if you have a `src` folder or `bin` folder or some other folder + # you want to upload. + # @return [String] + attr_accessor :dir + + def initialize + @host = UNSET_VALUE + @username = UNSET_VALUE + @password = UNSET_VALUE + @passive = UNSET_VALUE + @secure = UNSET_VALUE + @destination = UNSET_VALUE + + @includes = [] + @excludes = [] + + @dir = UNSET_VALUE + end + + def merge(other) + super.tap do |result| + result.includes = self.includes.dup.concat(other.includes).uniq + result.excludes = self.excludes.dup.concat(other.excludes).uniq + end + end + + def finalize! + @host = nil if @host == UNSET_VALUE + @username = nil if @username == UNSET_VALUE + @password = nil if @password == UNSET_VALUE + @passive = true if @passive == UNSET_VALUE + @secure = false if @secure == UNSET_VALUE + @destination = "/" if @destination == UNSET_VALUE + @dir = "." if @dir == UNSET_VALUE + end + + def validate(machine) + errors = _detected_errors + + if missing?(@host) + errors << I18n.t("ftp_push.errors.missing_attribute", + attribute: "host", + ) + end + + if missing?(@username) + errors << I18n.t("ftp_push.errors.missing_attribute", + attribute: "username", + ) + end + + if missing?(@destination) + errors << I18n.t("ftp_push.errors.missing_attribute", + attribute: "destination", + ) + end + + if missing?(@dir) + errors << I18n.t("ftp_push.errors.missing_attribute", + attribute: "dir", + ) + end + + { "FTP push" => errors } + end + + # Add the filepath to the list of includes + # @param [String] filepath + def include(filepath) + @includes << filepath + end + + # Add the filepath to the list of excludes + # @param [String] filepath + def exclude(filepath) + @excludes << filepath + end + + private + + # Determine if the given string is "missing" (blank) + # @return [true, false] + def missing?(obj) + obj.to_s.strip.empty? + end + end + end +end diff --git a/plugins/pushes/ftp/locales/en.yml b/plugins/pushes/ftp/locales/en.yml new file mode 100644 index 000000000..dcac3a427 --- /dev/null +++ b/plugins/pushes/ftp/locales/en.yml @@ -0,0 +1,9 @@ +en: + ftp_push: + errors: + missing_attribute: |- + Missing required attribute '%{attribute}'. The Vagrant FTP Push plugin + requires you set this attribute. Please set this attribute in your + Vagrantfile, for example: + + push.%{attribute} = "..." diff --git a/test/unit/plugins/pushes/ftp/config_test.rb b/test/unit/plugins/pushes/ftp/config_test.rb new file mode 100644 index 000000000..ef5010395 --- /dev/null +++ b/test/unit/plugins/pushes/ftp/config_test.rb @@ -0,0 +1,171 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/pushes/ftp/config") + +describe VagrantPlugins::FTPPush::Config do + include_context "unit" + + before(:all) do + I18n.load_path << Vagrant.source_root.join("plugins/pushes/ftp/locales/en.yml") + I18n.reload! + end + + subject { described_class.new } + + let(:machine) { double("machine") } + + describe "#host" do + it "defaults to nil" do + subject.finalize! + expect(subject.host).to be(nil) + end + end + + describe "#username" do + it "defaults to nil" do + subject.finalize! + expect(subject.username).to be(nil) + end + end + + describe "#password" do + it "defaults to nil" do + subject.finalize! + expect(subject.password).to be(nil) + end + end + + describe "#passive" do + it "defaults to true" do + subject.finalize! + expect(subject.passive).to be(true) + end + end + + describe "#secure" do + it "defaults to false" do + subject.finalize! + expect(subject.secure).to be(false) + end + end + + describe "#destination" do + it "defaults to /" do + subject.finalize! + expect(subject.destination).to eq("/") + end + end + + describe "#dir" do + it "defaults to nil" do + subject.finalize! + expect(subject.dir).to eq(".") + end + end + + describe "#merge" do + context "when includes are given" do + let(:one) { described_class.new } + let(:two) { described_class.new } + + it "merges the result" do + one.includes = %w(a b c) + two.includes = %w(c d e) + result = one.merge(two) + expect(result.includes).to eq(%w(a b c d e)) + end + end + + context "when excludes are given" do + let(:one) { described_class.new } + let(:two) { described_class.new } + + it "merges the result" do + one.excludes = %w(a b c) + two.excludes = %w(c d e) + result = one.merge(two) + expect(result.excludes).to eq(%w(a b c d e)) + end + end + end + + describe "#validate" do + before do + allow(machine).to receive(:env) + .and_return(double("env", + root_path: "", + )) + + subject.host = "ftp.example.com" + subject.username = "sethvargo" + subject.password = "bacon" + subject.destination = "/" + subject.dir = "." + end + + let(:result) { subject.validate(machine) } + let(:errors) { result["FTP push"] } + + context "when the host is missing" do + it "returns an error" do + subject.host = "" + subject.finalize! + expect(errors).to include(I18n.t("ftp_push.errors.missing_attribute", + attribute: "host", + )) + end + end + + context "when the username is missing" do + it "returns an error" do + subject.username = "" + subject.finalize! + expect(errors).to include(I18n.t("ftp_push.errors.missing_attribute", + attribute: "username", + )) + end + end + + context "when the password is missing" do + it "does not return an error" do + subject.password = "" + subject.finalize! + expect(errors).to be_empty + end + end + + context "when the destination is missing" do + it "returns an error" do + subject.destination = "" + subject.finalize! + expect(errors).to include(I18n.t("ftp_push.errors.missing_attribute", + attribute: "destination", + )) + end + end + + context "when the dir is missing" do + it "returns an error" do + subject.dir = "" + subject.finalize! + expect(errors).to include(I18n.t("ftp_push.errors.missing_attribute", + attribute: "dir", + )) + end + end + end + + describe "#include" do + it "adds the item to the list" do + subject.include("me") + expect(subject.includes).to include("me") + end + end + + describe "#exclude" do + it "adds the item to the list" do + subject.exclude("not me") + expect(subject.excludes).to include("not me") + end + end +end From eb5cecc782ee65507b929f7bf7e0e0a588ab3e27 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 11 Nov 2014 18:36:12 -0500 Subject: [PATCH 40/83] Create ftp push plugin with custom i18n loading --- plugins/pushes/ftp/plugin.rb | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 plugins/pushes/ftp/plugin.rb diff --git a/plugins/pushes/ftp/plugin.rb b/plugins/pushes/ftp/plugin.rb new file mode 100644 index 000000000..ef333807b --- /dev/null +++ b/plugins/pushes/ftp/plugin.rb @@ -0,0 +1,33 @@ +require "vagrant" + +module VagrantPlugins + module FTPPush + class Plugin < Vagrant.plugin("2") + name "ftp" + description <<-DESC + Deploy to a remote FTP or SFTP server. + DESC + + config(:ftp, :push) do + require File.expand_path("../config", __FILE__) + init! + Config + end + + push(:ftp) do + require File.expand_path("../push", __FILE__) + init! + Push + end + + protected + + def self.init! + return if defined?(@_init) + I18n.load_path << File.expand_path("../locales/en.yml", __FILE__) + I18n.reload! + @_init = true + end + end + end +end From 80851a887ff5d55e832539d7ad605a0356ed0b7e Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 11 Nov 2014 18:36:30 -0500 Subject: [PATCH 41/83] Create an Adapter to bridge the APIs between SFTP and FTP libraries --- plugins/pushes/ftp/adapter.rb | 107 ++++++++++++++++++ test/unit/plugins/pushes/ftp/adapter_test.rb | 111 +++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 plugins/pushes/ftp/adapter.rb create mode 100644 test/unit/plugins/pushes/ftp/adapter_test.rb diff --git a/plugins/pushes/ftp/adapter.rb b/plugins/pushes/ftp/adapter.rb new file mode 100644 index 000000000..f370d411f --- /dev/null +++ b/plugins/pushes/ftp/adapter.rb @@ -0,0 +1,107 @@ +module VagrantPlugins + module FTPPush + class Adapter + attr_reader :host + attr_reader :port + attr_reader :username + attr_reader :password + attr_reader :options + attr_reader :server + + def initialize(host, username, password, options = {}) + @host, @port = parse_host(host) + @username = username + @password = password + @options = options + @server = nil + end + + # Parse the host into it's url and port parts. + # @return [Array] + def parse_host(host) + if host.include?(":") + split = host.split(":", 2) + [split[0], split[1].to_i] + else + [host, default_port] + end + end + + def default_port + raise NotImplementedError + end + + def connect(&block) + raise NotImplementedError + end + + def upload(local, remote) + raise NotImplementedError + end + end + + # + # The FTP Adapter + # + class FTPAdapter < Adapter + def initialize(*) + require "net/ftp" + super + end + + def default_port + 20 + end + + def connect(&block) + @server = Net::FTP.new + @server.passive = options.fetch(:passive, true) + @server.connect(host, port) + @server.login(username, password) + + begin + yield self + ensure + @server.close + end + end + + def upload(local, remote) + parent = File.dirname(remote) + + # Create the parent directory if it does not exist + if !@server.list("/").any? { |f| f.start_with?(parent) } + @server.mkdir(parent) + end + + # Upload the file + @server.putbinaryfile(local, remote) + end + end + + # + # The SFTP Adapter + # + class SFTPAdapter < Adapter + def initialize(*) + require "net/sftp" + super + end + + def default_port + 22 + end + + def connect(&block) + Net::SFTP.start(@host, @username, password: @password, port: @port) do |server| + @server = server + yield self + end + end + + def upload(local, remote) + @server.upload!(local, remote, mkdir: true) + end + end + end +end diff --git a/test/unit/plugins/pushes/ftp/adapter_test.rb b/test/unit/plugins/pushes/ftp/adapter_test.rb new file mode 100644 index 000000000..54bacc1d1 --- /dev/null +++ b/test/unit/plugins/pushes/ftp/adapter_test.rb @@ -0,0 +1,111 @@ +require_relative "../../../base" +require "fake_ftp" + +require Vagrant.source_root.join("plugins/pushes/ftp/adapter") + +describe VagrantPlugins::FTPPush::Adapter do + include_context "unit" + + subject do + described_class.new("127.0.0.1:2345", "sethvargo", "bacon", + foo: "bar", + ) + end + + describe "#initialize" do + it "sets the instance variables" do + expect(subject.host).to eq("127.0.0.1") + expect(subject.port).to eq(2345) + expect(subject.username).to eq("sethvargo") + expect(subject.password).to eq("bacon") + expect(subject.options).to eq(foo: "bar") + expect(subject.server).to be(nil) + end + end + + describe "#parse_host" do + it "has a default value" do + allow(subject).to receive(:default_port) + .and_return(5555) + + result = subject.parse_host("127.0.0.1") + expect(result[0]).to eq("127.0.0.1") + expect(result[1]).to eq(5555) + end + end +end + +describe VagrantPlugins::FTPPush::FTPAdapter do + include_context "unit" + + before(:all) do + @server = FakeFtp::Server.new(21212, 21213) + @server.start + end + + after(:all) { @server.stop } + + let(:server) { @server } + + before { server.reset } + + subject do + described_class.new("127.0.0.1:#{server.port}", "sethvargo", "bacon") + end + + describe "#default_port" do + it "is 20" do + expect(subject.default_port).to eq(20) + end + end + + describe "#upload" do + before do + @dir = Dir.mktmpdir + FileUtils.touch("#{@dir}/file") + end + + after do + FileUtils.rm_rf(@dir) + end + + it "uploads the file" do + subject.connect do |ftp| + ftp.upload("#{@dir}/file", "/file") + end + + expect(server.files).to include("file") + end + + it "uploads in passive mode" do + subject.options[:passive] = true + subject.connect do |ftp| + ftp.upload("#{@dir}/file", "/file") + end + + expect(server.file("file")).to be_passive + end + end +end + +describe VagrantPlugins::FTPPush::SFTPAdapter do + include_context "unit" + + subject do + described_class.new("127.0.0.1:2345", "sethvargo", "bacon", + foo: "bar", + ) + end + + describe "#default_port" do + it "is 22" do + expect(subject.default_port).to eq(22) + end + end + + describe "#upload" do + it "uploads the file" do + pending "a way to mock an SFTP server" + end + end +end From 8aaf5dc5786252f0f3435402fe9d8f1f0faa5072 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 11 Nov 2014 18:36:42 -0500 Subject: [PATCH 42/83] Add the FTP push --- plugins/pushes/ftp/push.rb | 116 ++++++++ test/unit/plugins/pushes/ftp/push_test.rb | 313 ++++++++++++++++++++++ 2 files changed, 429 insertions(+) create mode 100644 plugins/pushes/ftp/push.rb create mode 100644 test/unit/plugins/pushes/ftp/push_test.rb diff --git a/plugins/pushes/ftp/push.rb b/plugins/pushes/ftp/push.rb new file mode 100644 index 000000000..3cf4169a0 --- /dev/null +++ b/plugins/pushes/ftp/push.rb @@ -0,0 +1,116 @@ +require "net/ftp" +require "pathname" + +require_relative "adapter" + +module VagrantPlugins + module FTPPush + class Push < Vagrant.plugin("2", :push) + IGNORED_FILES = %w(. ..).freeze + + def push + # Grab files early so if there's an exception or issue, we don't have to + # wait and close the (S)FTP connection as well + files = Hash[*all_files.flat_map do |file| + relative_path = relative_path_for(file, config.dir) + destination = File.expand_path(File.join(config.destination, relative_path)) + [file, destination] + end] + + connect do |ftp| + files.each do |local, remote| + ftp.upload(local, remote) + end + end + end + + # Helper method for creating the FTP or SFTP connection. + # @yield [Adapter] + def connect(&block) + klass = config.secure ? SFTPAdapter : FTPAdapter + ftp = klass.new(config.host, config.username, config.password, + passive: config.passive) + ftp.connect(&block) + end + + # Parse the host into it's url and port parts. + # @return [Array] + def parse_host(host) + if host.include?(":") + host.split(":", 2) + else + [host, "22"] + end + end + + # The list of all files that should be pushed by this push. This method + # only returns **files**, not folders or symlinks! + # @return [Array] + def all_files + files = glob("#{config.dir}/**/*") + includes_files + filter_excludes!(files, config.excludes) + files.reject! { |f| !File.file?(f) } + files + end + + # The list of files to include in addition to those specified in `dir`. + # @return [Array] + def includes_files + includes = config.includes.flat_map do |i| + path = absolute_path_for(i, config.dir) + [path, "#{path}/**/*"] + end + + glob("{#{includes.join(",")}}") + end + + # Filter the excludes out of the given list. This method modifies the + # given list in memory! + # + # @param [Array] list + # the filepaths + # @param [Array] excludes + # the exclude patterns or files + def filter_excludes!(list, excludes) + excludes = Array(excludes).flat_map { |e| [e, "#{e}/*"] } + list.reject! do |file| + basename = relative_path_for(file, config.dir) + + # Handle the special case where the file is outside of the working + # directory... + if basename.start_with?("../") + basename = file + end + + excludes.any? { |e| File.fnmatch?(e, basename, File::FNM_DOTMATCH) } + end + end + + # Get the list of files that match the given pattern. + # @return [Array] + def glob(pattern) + Dir.glob(pattern, File::FNM_DOTMATCH).sort.reject do |file| + IGNORED_FILES.include?(File.basename(file)) + end + end + + # The absolute path to the given `path` and `parent`, unless the given + # path is absolute. + # @return [String] + def absolute_path_for(path, parent) + path = Pathname.new(path) + return path if path.absolute? + File.expand_path(path, parent) + end + + # The relative path from the given `parent`. If files exist on another + # device, this will probably blow up. + # @return [String] + def relative_path_for(path, parent) + Pathname.new(path).relative_path_from(Pathname.new(parent)).to_s + rescue ArgumentError + return path + end + end + end +end diff --git a/test/unit/plugins/pushes/ftp/push_test.rb b/test/unit/plugins/pushes/ftp/push_test.rb new file mode 100644 index 000000000..ca8ee2752 --- /dev/null +++ b/test/unit/plugins/pushes/ftp/push_test.rb @@ -0,0 +1,313 @@ +require_relative "../../../base" +require "fake_ftp" + +require Vagrant.source_root.join("plugins/pushes/ftp/push") + +describe VagrantPlugins::FTPPush::Push do + include_context "unit" + + let(:env) { isolated_environment } + let(:config) do + double("config", + host: "127.0.0.1:21212", + username: "sethvargo", + password: "bacon", + passive: false, + secure: false, + destination: "/var/www/site", + ) + end + + subject { described_class.new(env, config) } + + describe "#push" do + before(:all) do + @server = FakeFtp::Server.new(21212, 21213) + @server.start + + @dir = Dir.mktmpdir + + FileUtils.touch("#{@dir}/.hidden.rb") + FileUtils.touch("#{@dir}/application.rb") + FileUtils.touch("#{@dir}/config.rb") + FileUtils.touch("#{@dir}/Gemfile") + FileUtils.touch("#{@dir}/data.txt") + FileUtils.mkdir("#{@dir}/empty_folder") + end + + after(:all) do + FileUtils.rm_rf(@dir) + @server.stop + end + + let(:server) { @server } + + before do + allow(config).to receive(:dir) + .and_return(@dir) + + allow(config).to receive(:includes) + .and_return([]) + + allow(config).to receive(:excludes) + .and_return(%w(*.rb)) + end + + + it "pushes the files to the server" do + subject.push + expect(server.files).to eq(%w(Gemfile data.txt)) + end + end + + describe "#connect" do + before do + allow_any_instance_of(VagrantPlugins::FTPPush::FTPAdapter) + .to receive(:connect) + .and_yield(:ftp) + allow_any_instance_of(VagrantPlugins::FTPPush::SFTPAdapter) + .to receive(:connect) + .and_yield(:sftp) + end + + context "when secure is requested" do + before do + allow(config).to receive(:secure) + .and_return(true) + end + + it "yields a new SFTPAdapter" do + expect { |b| subject.connect(&b) }.to yield_with_args(:sftp) + end + end + + context "when secure is not requested" do + before do + allow(config).to receive(:secure) + .and_return(false) + end + + it "yields a new FTPAdapter" do + expect { |b| subject.connect(&b) }.to yield_with_args(:ftp) + end + end + end + + describe "#parse_host" do + let(:result) { subject.parse_host(host) } + + context "when no port is given" do + let(:host) { "127.0.0.1" } + + it "returns the url and port 22" do + expect(result).to eq(["127.0.0.1", "22"]) + end + end + + context "when a port is given" do + let(:host) { "127.0.0.1:23456" } + + it "returns the url and port 23456" do + expect(result).to eq(["127.0.0.1", "23456"]) + end + end + + context "when more than more port is given" do + let(:host) { "127.0.0.1:22:33:44" } + + it "returns the url and everything after" do + expect(result).to eq(["127.0.0.1", "22:33:44"]) + end + end + end + + describe "#all_files" do + before(:all) do + @dir = Dir.mktmpdir + + FileUtils.touch("#{@dir}/.hidden.rb") + FileUtils.touch("#{@dir}/application.rb") + FileUtils.touch("#{@dir}/config.rb") + FileUtils.touch("#{@dir}/Gemfile") + FileUtils.mkdir("#{@dir}/empty_folder") + FileUtils.mkdir("#{@dir}/folder") + FileUtils.mkdir("#{@dir}/folder/.git") + FileUtils.touch("#{@dir}/folder/.git/config") + FileUtils.touch("#{@dir}/folder/server.rb") + end + + after(:all) do + FileUtils.rm_rf(@dir) + end + + let(:files) do + subject.all_files.map do |file| + file.sub("#{@dir}/", "") + end + end + + before do + allow(config).to receive(:dir) + .and_return(@dir) + + allow(config).to receive(:includes) + .and_return(%w(not_a_file.rb still_not_a_file.rb)) + + allow(config).to receive(:excludes) + .and_return(%w(*.rb)) + end + + it "returns the list of real files + includes, without excludes" do + expect(files).to eq(%w( + Gemfile + folder/.git/config + )) + end + end + + describe "includes_files" do + before(:all) do + @dir = Dir.mktmpdir + + FileUtils.touch("#{@dir}/.hidden.rb") + FileUtils.touch("#{@dir}/application.rb") + FileUtils.touch("#{@dir}/config.rb") + FileUtils.touch("#{@dir}/Gemfile") + FileUtils.mkdir("#{@dir}/folder") + FileUtils.mkdir("#{@dir}/folder/.git") + FileUtils.touch("#{@dir}/folder/.git/config") + FileUtils.touch("#{@dir}/folder/server.rb") + end + + after(:all) do + FileUtils.rm_rf(@dir) + end + + let(:files) do + subject.includes_files.map do |file| + file.sub("#{@dir}/", "") + end + end + + before do + allow(config).to receive(:dir) + .and_return(@dir) + end + + def set_includes(value) + allow(config).to receive(:includes) + .and_return(value) + end + + it "includes the file" do + set_includes(["Gemfile"]) + expect(files).to eq(%w( + Gemfile + )) + end + + it "includes the files that are subdirectories" do + set_includes(["folder"]) + expect(files).to eq(%w( + folder + folder/.git + folder/.git/config + folder/server.rb + )) + end + + it "includes files that match a pattern" do + set_includes(["*.rb"]) + expect(files).to eq(%w( + .hidden.rb + application.rb + config.rb + )) + end + end + + describe "#filter_excludes" do + let(:dir) { "/root/dir" } + + let(:list) do + %W( + #{dir}/.hidden.rb + #{dir}/application.rb + #{dir}/config.rb + #{dir}/Gemfile + #{dir}/folder + #{dir}/folder/.git + #{dir}/folder/.git/config + #{dir}/folder/server.rb + + /path/outside/you.rb + /path/outside/me.rb + /path/outside/folder/bacon.rb + ) + end + + before do + allow(config).to receive(:dir) + .and_return(dir) + end + + it "excludes files" do + subject.filter_excludes!(list, %w(*.rb)) + + expect(list).to eq(%W( + #{dir}/Gemfile + #{dir}/folder + #{dir}/folder/.git + #{dir}/folder/.git/config + )) + end + + it "excludes files in a directory" do + subject.filter_excludes!(list, %w(folder)) + + expect(list).to eq(%W( + #{dir}/.hidden.rb + #{dir}/application.rb + #{dir}/config.rb + #{dir}/Gemfile + + /path/outside/you.rb + /path/outside/me.rb + /path/outside/folder/bacon.rb + )) + end + + it "excludes specific files in a directory" do + subject.filter_excludes!(list, %w(/path/outside/folder/*.rb)) + + expect(list).to eq(%W( + #{dir}/.hidden.rb + #{dir}/application.rb + #{dir}/config.rb + #{dir}/Gemfile + #{dir}/folder + #{dir}/folder/.git + #{dir}/folder/.git/config + #{dir}/folder/server.rb + + /path/outside/you.rb + /path/outside/me.rb + )) + end + + it "excludes files outside the #dir" do + subject.filter_excludes!(list, %w(/path/outside)) + + expect(list).to eq(%W( + #{dir}/.hidden.rb + #{dir}/application.rb + #{dir}/config.rb + #{dir}/Gemfile + #{dir}/folder + #{dir}/folder/.git + #{dir}/folder/.git/config + #{dir}/folder/server.rb + )) + end + end +end From c8bdf53c7ebed13c8942ae0246cf4be9a7e3cf79 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 11 Nov 2014 18:58:02 -0500 Subject: [PATCH 43/83] Rename push environment to env --- lib/vagrant/plugin/v2/push.rb | 8 ++++---- plugins/pushes/harmony/push.rb | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/vagrant/plugin/v2/push.rb b/lib/vagrant/plugin/v2/push.rb index 002479b33..f8bc15d53 100644 --- a/lib/vagrant/plugin/v2/push.rb +++ b/lib/vagrant/plugin/v2/push.rb @@ -2,16 +2,16 @@ module Vagrant module Plugin module V2 class Push - attr_reader :environment + attr_reader :env attr_reader :config # Initializes the pusher with the given environment the push # configuration. # - # @param [environment] environment + # @param [Environment] env # @param [Object] config Push configuration - def initialize(environment, config) - @environment = environment + def initialize(env, config) + @env = env @config = config end diff --git a/plugins/pushes/harmony/push.rb b/plugins/pushes/harmony/push.rb index 9fd926fee..022c5db07 100644 --- a/plugins/pushes/harmony/push.rb +++ b/plugins/pushes/harmony/push.rb @@ -23,11 +23,11 @@ module VagrantPlugins # if we're on a system that doesn't support exec, so handle that properly. def execute(uploader) cmd = [] - cmd << "-vcs" if @config.vcs - cmd += @config.include.map { |v| ["-include", v] } - cmd += @config.exclude.map { |v| ["-exclude", v] } - cmd << @config.app - cmd << File.expand_path(@config.dir, @environment.root_path) + cmd << "-vcs" if config.vcs + cmd += config.include.map { |v| ["-include", v] } + cmd += config.exclude.map { |v| ["-exclude", v] } + cmd << config.app + cmd << File.expand_path(config.dir, env.root_path) Vagrant::Util::SafeExec.exec(uploader, *cmd.flatten) end @@ -37,7 +37,7 @@ module VagrantPlugins # @return [String] def uploader_path # Determine the uploader path - uploader = @config.uploader_path + uploader = config.uploader_path if uploader return uploader end From ed605c9aacc82b441a264c75e931d7b62328ff1c Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 12 Nov 2014 15:49:55 -0500 Subject: [PATCH 44/83] Rename Harmony to Atlas, minor config changes --- plugins/pushes/{harmony => atlas}/config.rb | 55 +++++-- plugins/pushes/{harmony => atlas}/errors.rb | 4 +- plugins/pushes/atlas/locales/en.yml | 11 ++ plugins/pushes/{harmony => atlas}/plugin.rb | 10 +- plugins/pushes/{harmony => atlas}/push.rb | 8 +- test/unit/plugins/pushes/atlas/config_test.rb | 135 ++++++++++++++++++ .../pushes/{harmony => atlas}/push_test.rb | 35 ++--- .../plugins/pushes/harmony/config_test.rb | 91 ------------ 8 files changed, 215 insertions(+), 134 deletions(-) rename plugins/pushes/{harmony => atlas}/config.rb (63%) rename plugins/pushes/{harmony => atlas}/errors.rb (74%) create mode 100644 plugins/pushes/atlas/locales/en.yml rename plugins/pushes/{harmony => atlas}/plugin.rb (79%) rename plugins/pushes/{harmony => atlas}/push.rb (88%) create mode 100644 test/unit/plugins/pushes/atlas/config_test.rb rename test/unit/plugins/pushes/{harmony => atlas}/push_test.rb (73%) delete mode 100644 test/unit/plugins/pushes/harmony/config_test.rb diff --git a/plugins/pushes/harmony/config.rb b/plugins/pushes/atlas/config.rb similarity index 63% rename from plugins/pushes/harmony/config.rb rename to plugins/pushes/atlas/config.rb index 6698cadec..71b40e299 100644 --- a/plugins/pushes/harmony/config.rb +++ b/plugins/pushes/atlas/config.rb @@ -1,5 +1,5 @@ module VagrantPlugins - module HarmonyPush + module AtlasPush class Config < Vagrant.plugin("2", :config) # The name of the application to push to. This will be created (with # user confirmation) if it doesn't already exist. @@ -23,8 +23,8 @@ module VagrantPlugins # to the directory being packaged. # # @return [Array] - attr_accessor :include - attr_accessor :exclude + attr_accessor :includes + attr_accessor :excludes # If set to true, Vagrant will automatically use VCS data to determine # the files to upload. As a caveat: uncommitted changes will not be @@ -45,20 +45,15 @@ module VagrantPlugins @app = UNSET_VALUE @dir = UNSET_VALUE @vcs = UNSET_VALUE - @include = [] - @exclude = [] + @includes = [] + @excludes = [] @uploader_path = UNSET_VALUE end def merge(other) super.tap do |result| - inc = self.include.dup - inc.concat(other.include) - result.include = inc - - exc = self.exclude.dup - exc.concat(other.exclude) - result.exclude = exc + result.includes = self.includes.dup.concat(other.includes).uniq + result.excludes = self.excludes.dup.concat(other.excludes).uniq end end @@ -72,11 +67,41 @@ module VagrantPlugins def validate(machine) errors = _detected_errors - if @app.to_s.strip.empty? - errors << I18n.t("push_harmony.errors.config.app_required") + if missing?(@app) + errors << I18n.t("atlas_push.errors.missing_attribute", + attribute: "app", + ) end - { "Harmony push" => errors } + if missing?(@dir) + errors << I18n.t("atlas_push.errors.missing_attribute", + attribute: "dir", + ) + end + + { "Atlas push" => errors } + end + + # Add the filepath to the list of includes + # @param [String] filepath + def include(filepath) + @includes << filepath + end + alias_method :include=, :include + + # Add the filepath to the list of excludes + # @param [String] filepath + def exclude(filepath) + @excludes << filepath + end + alias_method :exclude=, :exclude + + private + + # Determine if the given string is "missing" (blank) + # @return [true, false] + def missing?(obj) + obj.to_s.strip.empty? end end end diff --git a/plugins/pushes/harmony/errors.rb b/plugins/pushes/atlas/errors.rb similarity index 74% rename from plugins/pushes/harmony/errors.rb rename to plugins/pushes/atlas/errors.rb index 9eb590ff3..6fee42698 100644 --- a/plugins/pushes/harmony/errors.rb +++ b/plugins/pushes/atlas/errors.rb @@ -1,8 +1,8 @@ module VagrantPlugins - module HarmonyPush + module AtlasPush module Errors class Error < Vagrant::Errors::VagrantError - error_namespace("harmony_push.errors") + error_namespace("atlas_push.errors") end class UploaderNotFound < Error diff --git a/plugins/pushes/atlas/locales/en.yml b/plugins/pushes/atlas/locales/en.yml new file mode 100644 index 000000000..745e33bd2 --- /dev/null +++ b/plugins/pushes/atlas/locales/en.yml @@ -0,0 +1,11 @@ +en: + atlas_push: + errors: + missing_attribute: |- + Missing required attribute '%{attribute}'. The Vagrant Atlas Push plugin + requires you set this attribute. Please set this attribute in your + Vagrantfile, for example: + + config.push.define "atlas" do |push| + push.%{attribute} = "..." + end diff --git a/plugins/pushes/harmony/plugin.rb b/plugins/pushes/atlas/plugin.rb similarity index 79% rename from plugins/pushes/harmony/plugin.rb rename to plugins/pushes/atlas/plugin.rb index 331da4b32..b5c213a80 100644 --- a/plugins/pushes/harmony/plugin.rb +++ b/plugins/pushes/atlas/plugin.rb @@ -1,22 +1,22 @@ require "vagrant" module VagrantPlugins - module HarmonyPush + module AtlasPush autoload :Errors, File.expand_path("../errors", __FILE__) class Plugin < Vagrant.plugin("2") - name "harmony" + name "atlas" description <<-DESC - Deploy using HashiCorp's Harmony service. + Deploy using HashiCorp's Atlas service. DESC - config(:harmony, :push) do + config(:atlas, :push) do require_relative "config" init! Config end - push(:harmony) do + push(:atlas) do require_relative "push" init! Push diff --git a/plugins/pushes/harmony/push.rb b/plugins/pushes/atlas/push.rb similarity index 88% rename from plugins/pushes/harmony/push.rb rename to plugins/pushes/atlas/push.rb index 022c5db07..7dd64a358 100644 --- a/plugins/pushes/harmony/push.rb +++ b/plugins/pushes/atlas/push.rb @@ -3,9 +3,9 @@ require "vagrant/util/subprocess" require "vagrant/util/which" module VagrantPlugins - module HarmonyPush + module AtlasPush class Push < Vagrant.plugin("2", :push) - UPLOADER_BIN = "harmony-upload".freeze + UPLOADER_BIN = "atlas-upload".freeze def push uploader = self.uploader_path @@ -24,8 +24,8 @@ module VagrantPlugins def execute(uploader) cmd = [] cmd << "-vcs" if config.vcs - cmd += config.include.map { |v| ["-include", v] } - cmd += config.exclude.map { |v| ["-exclude", v] } + cmd += config.includes.map { |v| ["-include", v] } + cmd += config.excludes.map { |v| ["-exclude", v] } cmd << config.app cmd << File.expand_path(config.dir, env.root_path) Vagrant::Util::SafeExec.exec(uploader, *cmd.flatten) diff --git a/test/unit/plugins/pushes/atlas/config_test.rb b/test/unit/plugins/pushes/atlas/config_test.rb new file mode 100644 index 000000000..87411c472 --- /dev/null +++ b/test/unit/plugins/pushes/atlas/config_test.rb @@ -0,0 +1,135 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/pushes/atlas/config") + +describe VagrantPlugins::AtlasPush::Config do + include_context "unit" + + before(:all) do + I18n.load_path << Vagrant.source_root.join("plugins/pushes/atlas/locales/en.yml") + I18n.reload! + end + + let(:machine) { double("machine") } + + describe "#app" do + it "defaults to nil" do + subject.finalize! + expect(subject.app).to be(nil) + end + end + + describe "#dir" do + it "defaults to ." do + subject.finalize! + expect(subject.dir).to eq(".") + end + end + + describe "#vcs" do + it "defaults to true" do + subject.finalize! + expect(subject.vcs).to be(true) + end + end + + describe "#uploader_path" do + it "defaults to nil" do + subject.finalize! + expect(subject.uploader_path).to be(nil) + end + end + + describe "#validate" do + before do + allow(machine).to receive(:env) + .and_return(double("env", + root_path: "", + )) + + subject.app = "sethvargo/bacon" + subject.dir = "." + subject.vcs = true + subject.uploader_path = "uploader" + end + + let(:result) { subject.validate(machine) } + let(:errors) { result["Atlas push"] } + + context "when the app is missing" do + it "returns an error" do + subject.app = "" + subject.finalize! + expect(errors).to include(I18n.t("atlas_push.errors.missing_attribute", + attribute: "app", + )) + end + end + + context "when the dir is missing" do + it "returns an error" do + subject.dir = "" + subject.finalize! + expect(errors).to include(I18n.t("atlas_push.errors.missing_attribute", + attribute: "dir", + )) + end + end + + context "when the vcs is missing" do + it "does not return an error" do + subject.vcs = "" + subject.finalize! + expect(errors).to be_empty + end + end + + context "when the uploader_path is missing" do + it "returns an error" do + subject.uploader_path = "" + subject.finalize! + expect(errors).to be_empty + end + end + end + + describe "#merge" do + context "when includes are given" do + let(:one) { described_class.new } + let(:two) { described_class.new } + + it "merges the result" do + one.includes = %w(a b c) + two.includes = %w(c d e) + result = one.merge(two) + expect(result.includes).to eq(%w(a b c d e)) + end + end + + context "when excludes are given" do + let(:one) { described_class.new } + let(:two) { described_class.new } + + it "merges the result" do + one.excludes = %w(a b c) + two.excludes = %w(c d e) + result = one.merge(two) + expect(result.excludes).to eq(%w(a b c d e)) + end + end + end + + describe "#include" do + it "adds the item to the list" do + subject.include("me") + expect(subject.includes).to include("me") + end + end + + describe "#exclude" do + it "adds the item to the list" do + subject.exclude("not me") + expect(subject.excludes).to include("not me") + end + end +end diff --git a/test/unit/plugins/pushes/harmony/push_test.rb b/test/unit/plugins/pushes/atlas/push_test.rb similarity index 73% rename from test/unit/plugins/pushes/harmony/push_test.rb rename to test/unit/plugins/pushes/atlas/push_test.rb index bf365cc24..40bb9b8d1 100644 --- a/test/unit/plugins/pushes/harmony/push_test.rb +++ b/test/unit/plugins/pushes/atlas/push_test.rb @@ -1,27 +1,28 @@ require_relative "../../../base" -require Vagrant.source_root.join("plugins/pushes/harmony/config") -require Vagrant.source_root.join("plugins/pushes/harmony/push") +require Vagrant.source_root.join("plugins/pushes/atlas/config") +require Vagrant.source_root.join("plugins/pushes/atlas/push") -describe VagrantPlugins::HarmonyPush::Push do +describe VagrantPlugins::AtlasPush::Push do include_context "unit" + let(:env) do + double("env", + root_path: File.expand_path("..", __FILE__) + ) + end + let(:config) do - VagrantPlugins::HarmonyPush::Config.new.tap do |c| + VagrantPlugins::AtlasPush::Config.new.tap do |c| c.finalize! end end - let(:environment) { double("environment") } - - subject { described_class.new(environment, config) } + subject { described_class.new(env, config) } before do # Stub this right away to avoid real execs allow(Vagrant::Util::SafeExec).to receive(:exec) - - allow(environment).to receive(:root_path).and_return( - File.expand_path("../", __FILE__)) end describe "#push" do @@ -37,7 +38,7 @@ describe VagrantPlugins::HarmonyPush::Push do expect(subject).to receive(:uploader_path).and_return(nil) expect { subject.push }.to raise_error( - VagrantPlugins::HarmonyPush::Errors::UploaderNotFound) + VagrantPlugins::AtlasPush::Errors::UploaderNotFound) end end @@ -50,14 +51,14 @@ describe VagrantPlugins::HarmonyPush::Push do it "sends the basic flags" do expect(Vagrant::Util::SafeExec).to receive(:exec). - with("foo", "-vcs", app, environment.root_path.to_s) + with("foo", "-vcs", app, env.root_path.to_s) subject.execute("foo") end it "doesn't send VCS if disabled" do expect(Vagrant::Util::SafeExec).to receive(:exec). - with("foo", app, environment.root_path.to_s) + with("foo", app, env.root_path.to_s) config.vcs = false subject.execute("foo") @@ -66,18 +67,18 @@ describe VagrantPlugins::HarmonyPush::Push do it "sends includes" do expect(Vagrant::Util::SafeExec).to receive(:exec). with("foo", "-vcs", "-include", "foo", "-include", - "bar", app, environment.root_path.to_s) + "bar", app, env.root_path.to_s) - config.include = ["foo", "bar"] + config.includes = ["foo", "bar"] subject.execute("foo") end it "sends excludes" do expect(Vagrant::Util::SafeExec).to receive(:exec). with("foo", "-vcs", "-exclude", "foo", "-exclude", - "bar", app, environment.root_path.to_s) + "bar", app, env.root_path.to_s) - config.exclude = ["foo", "bar"] + config.excludes = ["foo", "bar"] subject.execute("foo") end end diff --git a/test/unit/plugins/pushes/harmony/config_test.rb b/test/unit/plugins/pushes/harmony/config_test.rb deleted file mode 100644 index 17cd4c1fe..000000000 --- a/test/unit/plugins/pushes/harmony/config_test.rb +++ /dev/null @@ -1,91 +0,0 @@ -require_relative "../../../base" - -require Vagrant.source_root.join("plugins/pushes/harmony/config") - -describe VagrantPlugins::HarmonyPush::Config do - include_context "unit" - - let(:machine) { double("machine") } - - # For testing merging - let(:one) { described_class.new } - let(:two) { described_class.new } - - def assert_invalid - errors = subject.validate(machine) - if !errors.values.any? { |v| !v.empty? } - raise "No errors: #{errors.inspect}" - end - end - - def assert_valid - errors = subject.validate(machine) - if !errors.values.all? { |v| v.empty? } - raise "Errors: #{errors.inspect}" - end - end - - def valid_defaults - end - - describe "defaults" do - before { subject.finalize! } - - its(:app) { should be_nil } - its(:dir) { should eq(".") } - its(:exclude) { should be_empty } - its(:include) { should be_empty } - its(:uploader_path) { should be_nil } - its(:vcs) { should be_true } - end - - describe "app" do - before do - valid_defaults - end - - it "is invalid if not set" do - subject.app = "" - subject.finalize! - assert_invalid - end - - it "is invalid if blank" do - subject.app = " " - subject.finalize! - assert_invalid - end - - it "is valid if set" do - subject.app = "foo/bar" - subject.finalize! - assert_valid - end - end - - describe "exclude" do - context "merge" do - subject { one.merge(two) } - - it "appends" do - one.exclude = ["foo"] - two.exclude = ["bar"] - - expect(subject.exclude).to eq(["foo", "bar"]) - end - end - end - - describe "include" do - context "merge" do - subject { one.merge(two) } - - it "appends" do - one.include = ["foo"] - two.include = ["bar"] - - expect(subject.include).to eq(["foo", "bar"]) - end - end - end -end From dc8b36b31dde5a60d1634c1286badb17975b70b4 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 12 Nov 2014 17:11:11 -0500 Subject: [PATCH 45/83] Allow = methods for include and exclude --- plugins/pushes/ftp/config.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/pushes/ftp/config.rb b/plugins/pushes/ftp/config.rb index 7ee1961a4..e493259db 100644 --- a/plugins/pushes/ftp/config.rb +++ b/plugins/pushes/ftp/config.rb @@ -109,12 +109,14 @@ module VagrantPlugins def include(filepath) @includes << filepath end + alias_method :include=, :include # Add the filepath to the list of excludes # @param [String] filepath def exclude(filepath) @excludes << filepath end + alias_method :exclude=, :exclude private From 8d090f2faa79ef637cb8ed5a39180db6eb1d8b4a Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 12 Nov 2014 17:11:41 -0500 Subject: [PATCH 46/83] Fix bad error message --- templates/locales/en.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 37ed84f07..a201a3833 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -950,8 +950,8 @@ en: 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 + config.push.define "ftp" do |push| + # ... push-specific options end push_strategy_not_defined: |- The push strategy '%{name}' is not defined in the Vagrantfile. Defined From 1d7f4f26be995a5d003e8ee1fa43c00d67f5acb6 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 12 Nov 2014 17:11:57 -0500 Subject: [PATCH 47/83] Provide a better error message for the FTP push --- plugins/pushes/ftp/locales/en.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/pushes/ftp/locales/en.yml b/plugins/pushes/ftp/locales/en.yml index dcac3a427..0bbbe51f1 100644 --- a/plugins/pushes/ftp/locales/en.yml +++ b/plugins/pushes/ftp/locales/en.yml @@ -6,4 +6,6 @@ en: requires you set this attribute. Please set this attribute in your Vagrantfile, for example: - push.%{attribute} = "..." + config.push.define "ftp" do |push| + push.%{attribute} = "..." + end From 7dd5b16218dc460daadaba0db33b742666ed3744 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 12 Nov 2014 17:12:13 -0500 Subject: [PATCH 48/83] Add preliminary website for pushes --- website/docs/source/layouts/layout.erb | 9 ++++ website/docs/source/v2/push/atlas.html.md | 56 ++++++++++++++++++++ website/docs/source/v2/push/ftp.html.md | 62 +++++++++++++++++++++++ website/docs/source/v2/push/index.html.md | 59 +++++++++++++++++++++ 4 files changed, 186 insertions(+) create mode 100644 website/docs/source/v2/push/atlas.html.md create mode 100644 website/docs/source/v2/push/ftp.html.md create mode 100644 website/docs/source/v2/push/index.html.md diff --git a/website/docs/source/layouts/layout.erb b/website/docs/source/layouts/layout.erb index 688b7507d..0631ad128 100644 --- a/website/docs/source/layouts/layout.erb +++ b/website/docs/source/layouts/layout.erb @@ -288,6 +288,15 @@ <% end %> + >Push + + <% if sidebar_section == "push" %> + + <% end %> + >Other <% if sidebar_section == "other" %> diff --git a/website/docs/source/v2/push/atlas.html.md b/website/docs/source/v2/push/atlas.html.md new file mode 100644 index 000000000..0d6347345 --- /dev/null +++ b/website/docs/source/v2/push/atlas.html.md @@ -0,0 +1,56 @@ +--- +page_title: "Vagrant Push - Atlas Strategy" +sidebar_current: "push-atlas" +description: |- + Atlas is HashiCorp's commercial offering to bring your Vagrant development + environments to production. The Vagrant Push Atlas strategy pushes your + application's code to HashiCorp's Atlas service. +--- + +# Vagrant Push + +## Atlas Strategy + +[Atlas][] is HashiCorp's commercial offering to bring your Vagrant development +environments to production. You can read more about HashiCorp's Atlas and all +its features on [the Atlas homepage][Atlas]. The Vagrant Push Atlas strategy +pushes your application's code to HashiCorp's Atlas service. + +The Vagrant Push Atlas strategy supports the following configuration options: + +- `app` - The name of the application in [HashiCorp's Atlas][Atlas]. If the + application does not exist, it will be created with user confirmation. + +- `exclude` - Add a file or file pattern to exclude from the upload, relative to + the `dir`. This value may be specified multiple times and is additive. + `exclude` take precedence over `include` values. + +- `include` - Add a file or file pattern to include in the upload, relative to + the `dir`. This value may be specified multiple times and is additive. + +- `dir` - The base directory containing the files to upload. By default this is + the same directory as the Vagrantfile, but you can specify this if you have + a `src` folder or `bin` folder or some other folder you want to upload. + +- `vsc` - If set to true, Vagrant will automatically use VCS data to determine + the files to upload. Uncommitted changes will not be deployed. + + +### Usage + +The Vagrant Push Atlas strategy is defined in the `Vagrantfile` using the +`atlas` key: + +```ruby +config.push.define "atlas" do |push| + push.app = "username/application" +end +``` + +And then push the application to Atlas: + +```shell +$ vagrant push +``` + +[Atlas]: https://atlas.hashicorp.com/ "HashiCorp's Atlas Service" diff --git a/website/docs/source/v2/push/ftp.html.md b/website/docs/source/v2/push/ftp.html.md new file mode 100644 index 000000000..54a2e1427 --- /dev/null +++ b/website/docs/source/v2/push/ftp.html.md @@ -0,0 +1,62 @@ +--- +page_title: "Vagrant Push - FTP & SFTP Strategy" +sidebar_current: "push-ftp" +description: |- + +--- + +# Vagrant Push + +## FTP & SFTP Strategy + +Vagrant Push FTP and SFTP strategy pushes the code in your Vagrant development +environment to a remote FTP or SFTP server. + +The Vagrant Push FTP And SFTP strategy supports the following configuration +options: + +- `host` - The address of the remote (S)FTP server. If the (S)FTP server is + running on a non-standard port, you can specify the port after the address + (`host:port`). + +- `username` - The username to use for authentication with the (S)FTP server. + +- `password` - The password to use for authentication with the (S)FTP server. + +- `passive` - Use passive FTP (default is true). + +- `secure` - Use secure (SFTP) (default is false). + +- `destination` - The root destination on the target system to sync the files + (default is `/`). + +- `exclude` - Add a file or file pattern to exclude from the upload, relative to + the `dir`. This value may be specified multiple times and is additive. + `exclude` take precedence over `include` values. + +- `include` - Add a file or file pattern to include in the upload, relative to + the `dir`. This value may be specified multiple times and is additive. + +- `dir` - The base directory containing the files to upload. By default this is + the same directory as the Vagrantfile, but you can specify this if you have + a `src` folder or `bin` folder or some other folder you want to upload. + + +### Usage + +The Vagrant Push FTP and SFTP strategy is defined in the `Vagrantfile` using the +`ftp` key: + +```ruby +config.push.define "ftp" do |push| + push.host = "ftp.company.com" + push.username = "username" + push.password = "password" +end +``` + +And then push the application to the FTP or SFTP server: + +```shell +$ vagrant push +``` diff --git a/website/docs/source/v2/push/index.html.md b/website/docs/source/v2/push/index.html.md new file mode 100644 index 000000000..224787ca6 --- /dev/null +++ b/website/docs/source/v2/push/index.html.md @@ -0,0 +1,59 @@ +--- +page_title: "Vagrant Push" +sidebar_current: "push" +description: |- + Vagrant Push is a revolutionary +--- + +# Vagrant Push + +As of version 1.8, Vagrant is capable of deploying or "pushing" application code +running as part of the Vagrant VM to a remote such as an FTP server or +[HashiCorp's Atlas][Atlas]. + +Pushes are defined in an application's `Vagrantfile` and are invoked using the +`vagrant push` subcommand. Much like other components of Vagrant, each Vagrant +Push plugin has its own configuration options. Please consult the documentation +for your Vagrant Push plugin for more information. Here is an example Vagrant +Push configuration section in a `Vagrantfile`: + +```ruby +config.push.define "ftp" do |push| + push.host = "ftp.company.com" + push.username = "..." + # ... +end +``` + +When the application is ready to be deployed to the FTP server, just run a +single command: + +```shell +$ vagrant push +``` + +Much like [Vagrant Providers][], Vagrant Push also supports multiple backend +declarations. Consider the common scenario of a staging and QA environment: + +```ruby +config.push.define "staging", strategy: "ftp" do |push| + # ... +end + +config.push.define "qa", strategy: "ftp" do |push| + # ... +end +``` + +In this scenario, the user must pass the name of the Vagrant Push to the +subcommand: + +```shell +$ vagrant push staging +``` + +Vagrant Push is the easiest way to deploy your application. You can read more +in the documentation links on the sidebar. + +[Atlas]: https://atlas.hashicorp.com/ "HashiCorp's Atlas Service" +[Vagrant Providers]: /v2/providers/index.html "Vagrant Providers" From fb055637621aa7218732cfb2137b6e2ab993c901 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 12 Nov 2014 17:14:58 -0500 Subject: [PATCH 49/83] Load the translations in Atlas --- plugins/pushes/atlas/plugin.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/pushes/atlas/plugin.rb b/plugins/pushes/atlas/plugin.rb index b5c213a80..2eee2517d 100644 --- a/plugins/pushes/atlas/plugin.rb +++ b/plugins/pushes/atlas/plugin.rb @@ -26,8 +26,7 @@ module VagrantPlugins def self.init! return if defined?(@_init) - I18n.load_path << File.expand_path( - "templates/locales/TODO.yml", Vagrant.source_root) + I18n.load_path << File.expand_path("../locales/en.yml", __FILE__) I18n.reload! @_init = true end From e38cf3152ca3d8562ce3a2915f8fd14a24436850 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 12 Nov 2014 17:26:52 -0500 Subject: [PATCH 50/83] Clarify what gets pushed --- website/docs/source/v2/push/index.html.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/source/v2/push/index.html.md b/website/docs/source/v2/push/index.html.md index 224787ca6..310039176 100644 --- a/website/docs/source/v2/push/index.html.md +++ b/website/docs/source/v2/push/index.html.md @@ -8,7 +8,7 @@ description: |- # Vagrant Push As of version 1.8, Vagrant is capable of deploying or "pushing" application code -running as part of the Vagrant VM to a remote such as an FTP server or +in the same directory as your Vagrantfile to a remote such as an FTP server or [HashiCorp's Atlas][Atlas]. Pushes are defined in an application's `Vagrantfile` and are invoked using the From ad15be2e1690e191d7ec9e46a0669bdc17acb40f Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Thu, 13 Nov 2014 17:07:23 -0500 Subject: [PATCH 51/83] Fix a typo in ftp config test --- test/unit/plugins/pushes/ftp/config_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/plugins/pushes/ftp/config_test.rb b/test/unit/plugins/pushes/ftp/config_test.rb index ef5010395..f66eeb791 100644 --- a/test/unit/plugins/pushes/ftp/config_test.rb +++ b/test/unit/plugins/pushes/ftp/config_test.rb @@ -57,7 +57,7 @@ describe VagrantPlugins::FTPPush::Config do end describe "#dir" do - it "defaults to nil" do + it "defaults to ." do subject.finalize! expect(subject.dir).to eq(".") end From d4058130e401fd50af0d0c88d022a9ab60d73387 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Thu, 13 Nov 2014 17:07:41 -0500 Subject: [PATCH 52/83] Add heroku config --- plugins/pushes/heroku/config.rb | 86 +++++++++++++ plugins/pushes/heroku/locales/en.yml | 30 +++++ .../unit/plugins/pushes/heroku/config_test.rb | 117 ++++++++++++++++++ 3 files changed, 233 insertions(+) create mode 100644 plugins/pushes/heroku/config.rb create mode 100644 plugins/pushes/heroku/locales/en.yml create mode 100644 test/unit/plugins/pushes/heroku/config_test.rb diff --git a/plugins/pushes/heroku/config.rb b/plugins/pushes/heroku/config.rb new file mode 100644 index 000000000..fd7b210ce --- /dev/null +++ b/plugins/pushes/heroku/config.rb @@ -0,0 +1,86 @@ +module VagrantPlugins + module HerokuPush + class Config < Vagrant.plugin("2", :config) + # The name of the Heroku application to push to. + # @return [String] + attr_accessor :app + + # The base directory with file contents to upload. By default this + # is the same directory as the Vagrantfile, but you can specify this + # if you have a `src` folder or `bin` folder or some other folder + # you want to upload. This directory must be a git repository. + # @return [String] + attr_accessor :dir + + # The path to the git binary to shell out to. This usually is only set for + # debugging/development. If not set, the git bin will be searched for + # in the PATH. + # @return [String] + attr_accessor :git_bin + + # The Git remote to push to (default: "heroku"). + # @return [String] + attr_accessor :remote + + # The Git branch to push to (default: "master"). + # @return [String] + attr_accessor :branch + + def initialize + @app = UNSET_VALUE + @dir = UNSET_VALUE + + @git_bin = UNSET_VALUE + @remote = UNSET_VALUE + @branch = UNSET_VALUE + end + + def finalize! + @app = nil if @app == UNSET_VALUE + @dir = "." if @dir == UNSET_VALUE + + @git_bin = "git" if @git_bin == UNSET_VALUE + @remote = "heroku" if @remote == UNSET_VALUE + @branch = "master" if @branch == UNSET_VALUE + end + + def validate(machine) + errors = _detected_errors + + if missing?(@dir) + errors << I18n.t("heroku_push.errors.missing_attribute", + attribute: "dir", + ) + end + + if missing?(@git_bin) + errors << I18n.t("heroku_push.errors.missing_attribute", + attribute: "git_bin", + ) + end + + if missing?(@remote) + errors << I18n.t("heroku_push.errors.missing_attribute", + attribute: "remote", + ) + end + + if missing?(@branch) + errors << I18n.t("heroku_push.errors.missing_attribute", + attribute: "branch", + ) + end + + { "Heroku push" => errors } + end + + private + + # Determine if the given string is "missing" (blank) + # @return [true, false] + def missing?(obj) + obj.to_s.strip.empty? + end + end + end +end diff --git a/plugins/pushes/heroku/locales/en.yml b/plugins/pushes/heroku/locales/en.yml new file mode 100644 index 000000000..e6f6bf441 --- /dev/null +++ b/plugins/pushes/heroku/locales/en.yml @@ -0,0 +1,30 @@ +en: + heroku_push: + errors: + command_failed: |- + The following command exited with a non-zero exit status: + + %{cmd} + + stdout: %{stdout} + stderr: %{stderr} + git_not_found: |- + The Git binary '%{bin}' could not be found. Please ensure you + have downloaded and installed the latest version of Git: + + http://git-scm.com/downloads + missing_attribute: |- + Missing required attribute '%{attribute}'. The Vagrant Heroku Push + plugin requires you set this attribute. Please set this attribute in + your Vagrantfile, for example: + + config.push.define "heroku" do |push| + push.%{attribute} = "..." + end + not_a_git_repo: |- + The following path is not a valid Git repository: + + %{path} + + Please ensure you are working in the correct directory. In order to use + the Vagrant Heroku Push plugin, you must have a git repository. diff --git a/test/unit/plugins/pushes/heroku/config_test.rb b/test/unit/plugins/pushes/heroku/config_test.rb new file mode 100644 index 000000000..0a157b17e --- /dev/null +++ b/test/unit/plugins/pushes/heroku/config_test.rb @@ -0,0 +1,117 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/pushes/heroku/config") + +describe VagrantPlugins::HerokuPush::Config do + include_context "unit" + + before(:all) do + I18n.load_path << Vagrant.source_root.join("plugins/pushes/heroku/locales/en.yml") + I18n.reload! + end + + subject { described_class.new } + + let(:machine) { double("machine") } + + describe "#app" do + it "defaults to nil" do + subject.finalize! + expect(subject.app).to be(nil) + end + end + + describe "#dir" do + it "defaults to ." do + subject.finalize! + expect(subject.dir).to eq(".") + end + end + + describe "#git_bin" do + it "defaults to git" do + subject.finalize! + expect(subject.git_bin).to eq("git") + end + end + + describe "#remote" do + it "defaults to git" do + subject.finalize! + expect(subject.remote).to eq("heroku") + end + end + + describe "#branch" do + it "defaults to git" do + subject.finalize! + expect(subject.branch).to eq("master") + end + end + + describe "#validate" do + before do + allow(machine).to receive(:env) + .and_return(double("env", + root_path: "", + )) + + subject.app = "bacon" + subject.dir = "." + subject.git_bin = "git" + subject.remote = "heroku" + subject.branch = "master" + end + + let(:result) { subject.validate(machine) } + let(:errors) { result["Heroku push"] } + + context "when the app is missing" do + it "does not return an error" do + subject.app = "" + subject.finalize! + expect(errors).to be_empty + end + end + + context "when the git_bin is missing" do + it "returns an error" do + subject.git_bin = "" + subject.finalize! + expect(errors).to include(I18n.t("heroku_push.errors.missing_attribute", + attribute: "git_bin", + )) + end + end + + context "when the remote is missing" do + it "returns an error" do + subject.remote = "" + subject.finalize! + expect(errors).to include(I18n.t("heroku_push.errors.missing_attribute", + attribute: "remote", + )) + end + end + + context "when the branch is missing" do + it "returns an error" do + subject.branch = "" + subject.finalize! + expect(errors).to include(I18n.t("heroku_push.errors.missing_attribute", + attribute: "branch", + )) + end + end + + context "when the dir is missing" do + it "returns an error" do + subject.dir = "" + subject.finalize! + expect(errors).to include(I18n.t("heroku_push.errors.missing_attribute", + attribute: "dir", + )) + end + end + end +end From c16dc5c9c9b2baaf11b8cc48f59763516ca150a8 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Thu, 13 Nov 2014 17:07:54 -0500 Subject: [PATCH 53/83] Add heroku push implementation --- plugins/pushes/heroku/errors.rb | 21 ++ plugins/pushes/heroku/push.rb | 110 ++++++++ test/unit/plugins/pushes/heroku/push_test.rb | 279 +++++++++++++++++++ 3 files changed, 410 insertions(+) create mode 100644 plugins/pushes/heroku/errors.rb create mode 100644 plugins/pushes/heroku/push.rb create mode 100644 test/unit/plugins/pushes/heroku/push_test.rb diff --git a/plugins/pushes/heroku/errors.rb b/plugins/pushes/heroku/errors.rb new file mode 100644 index 000000000..c92a7b76c --- /dev/null +++ b/plugins/pushes/heroku/errors.rb @@ -0,0 +1,21 @@ +module VagrantPlugins + module HerokuPush + module Errors + class Error < Vagrant::Errors::VagrantError + error_namespace("heroku_push.errors") + end + + class CommandFailed < Error + error_key(:command_failed) + end + + class GitNotFound < Error + error_key(:git_not_found) + end + + class NotAGitRepo < Error + error_key(:not_a_git_repo) + end + end + end +end diff --git a/plugins/pushes/heroku/push.rb b/plugins/pushes/heroku/push.rb new file mode 100644 index 000000000..8f50c4580 --- /dev/null +++ b/plugins/pushes/heroku/push.rb @@ -0,0 +1,110 @@ +require "vagrant/util/safe_exec" +require "vagrant/util/subprocess" +require "vagrant/util/which" + +require_relative "errors" + +module VagrantPlugins + module HerokuPush + class Push < Vagrant.plugin("2", :push) + def push + # Expand any paths relative to the root + dir = File.expand_path(config.dir, env.root_path) + + # Verify git is installed + verify_git_bin!(config.git_bin) + + # Verify we are operating in a git repo + verify_git_repo!(dir) + + # Check if we need to add the git remote + if !has_git_remote?(config.remote, dir) + add_heroku_git_remote(config.remote, config.app, dir) + end + + # Push to Heroku + git_push_heroku(config.remote, config.branch, dir) + end + + # Verify that git is installed. + # @raise [Errors::GitNotFound] + def verify_git_bin!(path) + if Vagrant::Util::Which.which(path).nil? + raise Errors::GitNotFound, bin: path + end + end + + # Verify that the given path is a git directory. + # @raise [Errors::NotAGitRepo] + # @param [String] + def verify_git_repo!(path) + if !File.directory?(git_dir(path)) + raise Errors::NotAGitRepo, path: path + end + end + + # The git directory for the given path. + # @param [String] path + # @return [String] + def git_dir(path) + "#{path}/.git" + end + + # Push to the Heroku remote. + # @param [String] remote + # @param [String] branch + def git_push_heroku(remote, branch, path) + execute!("git", + "--git-dir", git_dir(path), + "--work-tree", path, + "push", remote, branch, + ) + end + + # Check if the git remote has the given remote. + # @param [String] remote + # @return [true, false] + def has_git_remote?(remote, path) + result = execute!("git", + "--git-dir", git_dir(path), + "--work-tree", path, + "remote", + ) + remotes = result.stdout.split(/\r?\n/).map(&:strip) + remotes.include?(remote.to_s) + end + + # Add the Heroku to the current repository. + # @param [String] remote + # @param [String] app + def add_heroku_git_remote(remote, app, path) + execute!("git", + "--git-dir", git_dir(path), + "--work-tree", path, + "remote", "add", remote, heroku_git_url(app), + ) + end + + # The URL for this project on Heroku. + # @return [String] + def heroku_git_url(app) + "git@heroku.com:#{app}.git" + end + + # Execute the command, raising an exception if it fails. + # @return [Vagrant::Util::Subprocess::Result] + def execute!(*cmd) + result = Vagrant::Util::Subprocess.execute(*cmd) + + if result.exit_code != 0 + raise Errors::CommandFailed, + cmd: cmd.join(" "), + stdout: result.stdout, + stderr: result.stderr + end + + result + end + end + end +end diff --git a/test/unit/plugins/pushes/heroku/push_test.rb b/test/unit/plugins/pushes/heroku/push_test.rb new file mode 100644 index 000000000..1bec15927 --- /dev/null +++ b/test/unit/plugins/pushes/heroku/push_test.rb @@ -0,0 +1,279 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/pushes/heroku/push") + +describe VagrantPlugins::HerokuPush::Push do + include_context "unit" + + before(:all) do + I18n.load_path << Vagrant.source_root.join("plugins/pushes/heroku/locales/en.yml") + I18n.reload! + end + + let(:env) { isolated_environment } + let(:config) do + double("config", + app: "bacon", + dir: "lib", + git_bin: "git", + remote: "heroku", + branch: "master", + ) + end + + subject { described_class.new(env, config) } + + describe "#push" do + let(:root_path) { "/handy/dandy" } + let(:dir) { "#{root_path}/#{config.dir}" } + + before do + allow(subject).to receive(:verify_git_bin!) + allow(subject).to receive(:verify_git_repo!) + allow(subject).to receive(:has_git_remote?) + allow(subject).to receive(:add_heroku_git_remote) + allow(subject).to receive(:git_push_heroku) + allow(subject).to receive(:execute!) + + allow(env).to receive(:root_path) + .and_return(root_path) + end + + it "verifies the git bin is present" do + expect(subject).to receive(:verify_git_bin!) + .with(config.git_bin) + subject.push + end + + it "verifies the directory is a git repo" do + expect(subject).to receive(:verify_git_repo!) + .with(dir) + subject.push + end + + context "when the heroku remote exists" do + before do + allow(subject).to receive(:has_git_remote?) + .and_return(true) + end + + it "does not add the heroku remote" do + expect(subject).to_not receive(:add_heroku_git_remote) + subject.push + end + end + + context "when the heroku remote does not exist" do + before do + allow(subject).to receive(:has_git_remote?) + .and_return(false) + end + + it "adds the heroku remote" do + expect(subject).to receive(:add_heroku_git_remote) + .with(config.remote, config.app, dir) + subject.push + end + end + + it "pushes to heroku" do + expect(subject).to receive(:git_push_heroku) + .with(config.remote, config.branch, dir) + subject.push + end + end + + describe "#verify_git_bin!" do + context "when git does not exist" do + before do + allow(Vagrant::Util::Which).to receive(:which) + .with("git") + .and_return(nil) + end + + it "raises an exception" do + expect { + subject.verify_git_bin!("git") + } .to raise_error(VagrantPlugins::HerokuPush::Errors::GitNotFound) { |error| + expect(error.message).to eq(I18n.t("heroku_push.errors.git_not_found", + bin: "git", + )) + } + end + end + + context "when git exists" do + before do + allow(Vagrant::Util::Which).to receive(:which) + .with("git") + .and_return("git") + end + + it "does not raise an exception" do + expect { subject.verify_git_bin!("git") }.to_not raise_error + end + end + end + + describe "#verify_git_repo!" do + context "when the path is a git repo" do + before do + allow(File).to receive(:directory?) + .with("/repo/path/.git") + .and_return(false) + end + + it "raises an exception" do + expect { + subject.verify_git_repo!("/repo/path") + } .to raise_error(VagrantPlugins::HerokuPush::Errors::NotAGitRepo) { |error| + expect(error.message).to eq(I18n.t("heroku_push.errors.not_a_git_repo", + path: "/repo/path", + )) + } + end + end + + context "when the path is not a git repo" do + before do + allow(File).to receive(:directory?) + .with("/repo/path/.git") + .and_return(true) + end + + it "does not raise an exception" do + expect { subject.verify_git_repo!("/repo/path") }.to_not raise_error + end + end + end + + describe "#git_push_heroku" do + let(:dir) { "." } + + before { allow(subject).to receive(:execute!) } + + it "executes the proper command" do + expect(subject).to receive(:execute!) + .with("git", + "--git-dir", "#{dir}/.git", + "--work-tree", dir, + "push", "bacon", "hamlet", + ) + subject.git_push_heroku("bacon", "hamlet", dir) + end + end + + describe "#has_git_remote?" do + let(:dir) { "." } + + let(:process) do + double("process", + stdout: "origin\r\nbacon\nhello" + ) + end + + before do + allow(subject).to receive(:execute!) + .and_return(process) + end + + it "executes the proper command" do + expect(subject).to receive(:execute!) + .with("git", + "--git-dir", "#{dir}/.git", + "--work-tree", dir, + "remote", + ) + subject.has_git_remote?("bacon", dir) + end + + it "returns true when the remote exists" do + expect(subject.has_git_remote?("origin", dir)).to be(true) + expect(subject.has_git_remote?("bacon", dir)).to be(true) + expect(subject.has_git_remote?("hello", dir)).to be(true) + end + + it "returns false when the remote does not exist" do + expect(subject.has_git_remote?("nope", dir)).to be(false) + end + end + + describe "#add_heroku_git_remote" do + let(:dir) { "." } + + before do + allow(subject).to receive(:execute!) + allow(subject).to receive(:heroku_git_url) + .with("app") + .and_return("HEROKU_URL") + end + + it "executes the proper command" do + expect(subject).to receive(:execute!) + .with("git", + "--git-dir", "#{dir}/.git", + "--work-tree", dir, + "remote", "add", "bacon", "HEROKU_URL", + ) + subject.add_heroku_git_remote("bacon", "app", dir) + end + end + + describe "#heroku_git_url" do + it "returns the proper string" do + expect(subject.heroku_git_url("bacon")) + .to eq("git@heroku.com:bacon.git") + end + end + + describe "#git_dir" do + it "returns the .git directory for the path" do + expect(subject.git_dir("/path")).to eq("/path/.git") + end + end + + describe "#execute!" do + let(:exit_code) { 0 } + let(:stdout) { "This is the output" } + let(:stderr) { "This is the errput" } + + let(:process) do + double("process", + exit_code: exit_code, + stdout: stdout, + stderr: stderr, + ) + end + + before do + allow(Vagrant::Util::Subprocess).to receive(:execute) + .and_return(process) + end + + it "creates a subprocess" do + expect(Vagrant::Util::Subprocess).to receive(:execute) + expect { subject.execute! }.to_not raise_error + end + + it "returns the resulting process" do + expect(subject.execute!).to be(process) + end + + context "when the exit code is non-zero" do + let(:exit_code) { 1 } + + it "raises an exception" do + klass = VagrantPlugins::HerokuPush::Errors::CommandFailed + cmd = ["foo", "bar"] + + expect { subject.execute!(*cmd) }.to raise_error(klass) { |error| + expect(error.message).to eq(I18n.t("heroku_push.errors.command_failed", + cmd: cmd.join(" "), + stdout: stdout, + stderr: stderr, + )) + } + end + end + end +end From 4282fcf55e9159860c1b81f558e8f82c898c36a0 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Thu, 13 Nov 2014 17:08:34 -0500 Subject: [PATCH 54/83] Add heroku push plugin file --- plugins/pushes/heroku/plugin.rb | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 plugins/pushes/heroku/plugin.rb diff --git a/plugins/pushes/heroku/plugin.rb b/plugins/pushes/heroku/plugin.rb new file mode 100644 index 000000000..40865ee5d --- /dev/null +++ b/plugins/pushes/heroku/plugin.rb @@ -0,0 +1,33 @@ +require "vagrant" + +module VagrantPlugins + module HerokuPush + class Plugin < Vagrant.plugin("2") + name "heroku" + description <<-DESC + Deploy to a Heroku + DESC + + config(:heroku, :push) do + require File.expand_path("../config", __FILE__) + init! + Config + end + + push(:heroku) do + require File.expand_path("../push", __FILE__) + init! + Push + end + + protected + + def self.init! + return if defined?(@_init) + I18n.load_path << File.expand_path("../locales/en.yml", __FILE__) + I18n.reload! + @_init = true + end + end + end +end From ce380a3b121d14817c1191ee040b9cf2147b2a00 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Thu, 13 Nov 2014 17:11:24 -0500 Subject: [PATCH 55/83] Remove safe_exec (not being used) --- plugins/pushes/heroku/push.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/pushes/heroku/push.rb b/plugins/pushes/heroku/push.rb index 8f50c4580..cab46de7a 100644 --- a/plugins/pushes/heroku/push.rb +++ b/plugins/pushes/heroku/push.rb @@ -1,4 +1,3 @@ -require "vagrant/util/safe_exec" require "vagrant/util/subprocess" require "vagrant/util/which" From 1f49b7ef62c1771adc38d040f1cf235b5aa298a5 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Thu, 13 Nov 2014 17:46:54 -0500 Subject: [PATCH 56/83] Remove branch option (it should be interpreted) --- plugins/pushes/heroku/config.rb | 12 ------------ test/unit/plugins/pushes/heroku/config_test.rb | 18 ------------------ 2 files changed, 30 deletions(-) diff --git a/plugins/pushes/heroku/config.rb b/plugins/pushes/heroku/config.rb index fd7b210ce..927c1a7b9 100644 --- a/plugins/pushes/heroku/config.rb +++ b/plugins/pushes/heroku/config.rb @@ -22,17 +22,12 @@ module VagrantPlugins # @return [String] attr_accessor :remote - # The Git branch to push to (default: "master"). - # @return [String] - attr_accessor :branch - def initialize @app = UNSET_VALUE @dir = UNSET_VALUE @git_bin = UNSET_VALUE @remote = UNSET_VALUE - @branch = UNSET_VALUE end def finalize! @@ -41,7 +36,6 @@ module VagrantPlugins @git_bin = "git" if @git_bin == UNSET_VALUE @remote = "heroku" if @remote == UNSET_VALUE - @branch = "master" if @branch == UNSET_VALUE end def validate(machine) @@ -65,12 +59,6 @@ module VagrantPlugins ) end - if missing?(@branch) - errors << I18n.t("heroku_push.errors.missing_attribute", - attribute: "branch", - ) - end - { "Heroku push" => errors } end diff --git a/test/unit/plugins/pushes/heroku/config_test.rb b/test/unit/plugins/pushes/heroku/config_test.rb index 0a157b17e..e451ad152 100644 --- a/test/unit/plugins/pushes/heroku/config_test.rb +++ b/test/unit/plugins/pushes/heroku/config_test.rb @@ -42,13 +42,6 @@ describe VagrantPlugins::HerokuPush::Config do end end - describe "#branch" do - it "defaults to git" do - subject.finalize! - expect(subject.branch).to eq("master") - end - end - describe "#validate" do before do allow(machine).to receive(:env) @@ -60,7 +53,6 @@ describe VagrantPlugins::HerokuPush::Config do subject.dir = "." subject.git_bin = "git" subject.remote = "heroku" - subject.branch = "master" end let(:result) { subject.validate(machine) } @@ -94,16 +86,6 @@ describe VagrantPlugins::HerokuPush::Config do end end - context "when the branch is missing" do - it "returns an error" do - subject.branch = "" - subject.finalize! - expect(errors).to include(I18n.t("heroku_push.errors.missing_attribute", - attribute: "branch", - )) - end - end - context "when the dir is missing" do it "returns an error" do subject.dir = "" From b9e8f6e892db97ac09a0cbaa1bdbb5862729e98f Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Thu, 13 Nov 2014 17:47:08 -0500 Subject: [PATCH 57/83] Interpret the current branch to push to Heroku --- plugins/pushes/heroku/push.rb | 21 ++++++++- test/unit/plugins/pushes/heroku/push_test.rb | 45 ++++++++++++++++++-- 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/plugins/pushes/heroku/push.rb b/plugins/pushes/heroku/push.rb index cab46de7a..17dc75a04 100644 --- a/plugins/pushes/heroku/push.rb +++ b/plugins/pushes/heroku/push.rb @@ -10,6 +10,9 @@ module VagrantPlugins # Expand any paths relative to the root dir = File.expand_path(config.dir, env.root_path) + # Get the current branch + branch = git_branch(dir) + # Verify git is installed verify_git_bin!(config.git_bin) @@ -22,7 +25,7 @@ module VagrantPlugins end # Push to Heroku - git_push_heroku(config.remote, config.branch, dir) + git_push_heroku(config.remote, branch, dir) end # Verify that git is installed. @@ -49,6 +52,20 @@ module VagrantPlugins "#{path}/.git" end + # The name of the current git branch. + # @param [String] path + # @return [String] + def git_branch(path) + result = execute!("git", + "--git-dir", git_dir(path), + "--work-tree", path, + "branch", + ) + + # Returns something like "* master" + result.stdout.sub("*", "").strip + end + # Push to the Heroku remote. # @param [String] remote # @param [String] branch @@ -56,7 +73,7 @@ module VagrantPlugins execute!("git", "--git-dir", git_dir(path), "--work-tree", path, - "push", remote, branch, + "push", remote, "#{branch}:master", ) end diff --git a/test/unit/plugins/pushes/heroku/push_test.rb b/test/unit/plugins/pushes/heroku/push_test.rb index 1bec15927..ab5a64eee 100644 --- a/test/unit/plugins/pushes/heroku/push_test.rb +++ b/test/unit/plugins/pushes/heroku/push_test.rb @@ -17,17 +17,20 @@ describe VagrantPlugins::HerokuPush::Push do dir: "lib", git_bin: "git", remote: "heroku", - branch: "master", ) end subject { described_class.new(env, config) } describe "#push" do + let(:branch) { "master" } + let(:root_path) { "/handy/dandy" } let(:dir) { "#{root_path}/#{config.dir}" } before do + allow(subject).to receive(:git_branch) + .and_return(branch) allow(subject).to receive(:verify_git_bin!) allow(subject).to receive(:verify_git_repo!) allow(subject).to receive(:has_git_remote?) @@ -78,7 +81,7 @@ describe VagrantPlugins::HerokuPush::Push do it "pushes to heroku" do expect(subject).to receive(:git_push_heroku) - .with(config.remote, config.branch, dir) + .with(config.remote, branch, dir) subject.push end end @@ -157,7 +160,7 @@ describe VagrantPlugins::HerokuPush::Push do .with("git", "--git-dir", "#{dir}/.git", "--work-tree", dir, - "push", "bacon", "hamlet", + "push", "bacon", "hamlet:master", ) subject.git_push_heroku("bacon", "hamlet", dir) end @@ -232,6 +235,42 @@ describe VagrantPlugins::HerokuPush::Push do end end + describe "#git_branch" do + let(:stdout) { "" } + let(:process) { double("process", stdout: stdout) } + + before do + allow(subject).to receive(:execute!) + .and_return(process) + end + + let(:branch) { subject.git_branch("/path") } + + context "when the branch is prefixed with a star" do + let(:stdout) { "*bacon" } + + it "returns the correct name" do + expect(branch).to eq("bacon") + end + end + + context "when the branch is prefixed with a star space" do + let(:stdout) { "* bacon" } + + it "returns the correct name" do + expect(branch).to eq("bacon") + end + end + + context "when the branch is not prefixed" do + let(:stdout) { "bacon" } + + it "returns the correct name" do + expect(branch).to eq("bacon") + end + end + end + describe "#execute!" do let(:exit_code) { 0 } let(:stdout) { "This is the output" } From ea512a95f3d4b19fc12d72b5e283e681d2439c0e Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Thu, 13 Nov 2014 17:47:16 -0500 Subject: [PATCH 58/83] Add Heroku push docs --- website/docs/source/layouts/layout.erb | 1 + website/docs/source/v2/push/heroku.html.md | 63 ++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 website/docs/source/v2/push/heroku.html.md diff --git a/website/docs/source/layouts/layout.erb b/website/docs/source/layouts/layout.erb index 0631ad128..315408de5 100644 --- a/website/docs/source/layouts/layout.erb +++ b/website/docs/source/layouts/layout.erb @@ -294,6 +294,7 @@ <% end %> diff --git a/website/docs/source/v2/push/heroku.html.md b/website/docs/source/v2/push/heroku.html.md new file mode 100644 index 000000000..dc730dc62 --- /dev/null +++ b/website/docs/source/v2/push/heroku.html.md @@ -0,0 +1,63 @@ +--- +page_title: "Vagrant Push - Heroku Strategy" +sidebar_current: "push-heroku" +description: |- + The Vagrant Push Heroku strategy pushes your application's code to Heroku. + Only files which are committed to the Git repository are pushed to Heroku. +--- + +# Vagrant Push + +## Heroku Strategy + +[Heroku][] is a public IAAS provider that makes it easy to deploy an +application. The Vagrant Push Heroku strategy pushes your application's code to +Heroku. + +
+

+ Warning: The Vagrant Push Heroku strategy requires you + have configured your Heroku credentials and created the Heroku application. + This documentation will not cover these prerequisites, but you can read more + about them in the Heroku documentation. +

+
+ +Only files which are committed to the Git repository will be pushed to Heroku. +Additionally, the current working branch is always pushed to the Heroku, even if +it is not the "master" branch. + +The Vagrant Push Heroku strategy supports the following configuration options: + +- `app` - The name of the Heroku application. If the Heroku application does not + exist, an exception will be raised. If this value is not specified, the + basename of the directory containing the `Vagrantfile` is assumed to be the + name of the Heroku application. Since this value can change between users, it + is highly recommended that you add the `app` setting to your `Vagrantfile`. + +- `dir` - The base directory containing the Git repository to upload to Heroku. + By default this is the same directory as the Vagrantfile, but you can specify + this if you have a nested Git directory. + +- `remote` - The name of the Git remote where Heroku is configured. The default + value is "heroku". + + +### Usage + +The Vagrant Push Heroku strategy is defined in the `Vagrantfile` using the +`heroku` key: + +```ruby +config.push.define "heroku" do |push| + push.app = "my_application" +end +``` + +And then push the application to Heroku: + +```shell +$ vagrant push +``` + +[Heroku]: https://heroku.com/ "Heroku" From 7e3de3951e6d403887c646919392785c21267858 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Thu, 13 Nov 2014 18:00:30 -0500 Subject: [PATCH 59/83] Do not check for a branch until after we have found git --- plugins/pushes/heroku/push.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/pushes/heroku/push.rb b/plugins/pushes/heroku/push.rb index 17dc75a04..360db9af0 100644 --- a/plugins/pushes/heroku/push.rb +++ b/plugins/pushes/heroku/push.rb @@ -10,15 +10,15 @@ module VagrantPlugins # Expand any paths relative to the root dir = File.expand_path(config.dir, env.root_path) - # Get the current branch - branch = git_branch(dir) - # Verify git is installed verify_git_bin!(config.git_bin) # Verify we are operating in a git repo verify_git_repo!(dir) + # Get the current branch + branch = git_branch(dir) + # Check if we need to add the git remote if !has_git_remote?(config.remote, dir) add_heroku_git_remote(config.remote, config.app, dir) From 9d1a43c7668f38f845421fd66fe1a46246d3dd42 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Thu, 13 Nov 2014 18:00:43 -0500 Subject: [PATCH 60/83] Interpret the app from the CWD --- plugins/pushes/heroku/push.rb | 12 +++++++++++- test/unit/plugins/pushes/heroku/push_test.rb | 6 ++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/plugins/pushes/heroku/push.rb b/plugins/pushes/heroku/push.rb index 360db9af0..b894eba81 100644 --- a/plugins/pushes/heroku/push.rb +++ b/plugins/pushes/heroku/push.rb @@ -19,9 +19,12 @@ module VagrantPlugins # Get the current branch branch = git_branch(dir) + # Get the name of the app + app = config.app || interpret_app(dir) + # Check if we need to add the git remote if !has_git_remote?(config.remote, dir) - add_heroku_git_remote(config.remote, config.app, dir) + add_heroku_git_remote(config.remote, app, dir) end # Push to Heroku @@ -45,6 +48,13 @@ module VagrantPlugins end end + # Interpret the name of the Heroku application from the given path. + # @param [String] path + # @return [String] + def interpret_app(path) + File.basename(path) + end + # The git directory for the given path. # @param [String] path # @return [String] diff --git a/test/unit/plugins/pushes/heroku/push_test.rb b/test/unit/plugins/pushes/heroku/push_test.rb index ab5a64eee..c0337e41f 100644 --- a/test/unit/plugins/pushes/heroku/push_test.rb +++ b/test/unit/plugins/pushes/heroku/push_test.rb @@ -222,6 +222,12 @@ describe VagrantPlugins::HerokuPush::Push do end end + describe "#interpret_app" do + it "returns the basename of the directory" do + expect(subject.interpret_app("/foo/bar/blitz")).to eq("blitz") + end + end + describe "#heroku_git_url" do it "returns the proper string" do expect(subject.heroku_git_url("bacon")) From 612eeb22654ebecacc6e367773cef7603afd8eec Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Fri, 14 Nov 2014 15:51:35 -0500 Subject: [PATCH 61/83] Add local-exec push --- plugins/pushes/local-exec/config.rb | 37 +++++++++ plugins/pushes/local-exec/errors.rb | 13 ++++ plugins/pushes/local-exec/locales/en.yml | 18 +++++ plugins/pushes/local-exec/plugin.rb | 33 ++++++++ plugins/pushes/local-exec/push.rb | 28 +++++++ .../plugins/pushes/local-exec/config_test.rb | 45 +++++++++++ .../plugins/pushes/local-exec/push_test.rb | 78 +++++++++++++++++++ 7 files changed, 252 insertions(+) create mode 100644 plugins/pushes/local-exec/config.rb create mode 100644 plugins/pushes/local-exec/errors.rb create mode 100644 plugins/pushes/local-exec/locales/en.yml create mode 100644 plugins/pushes/local-exec/plugin.rb create mode 100644 plugins/pushes/local-exec/push.rb create mode 100644 test/unit/plugins/pushes/local-exec/config_test.rb create mode 100644 test/unit/plugins/pushes/local-exec/push_test.rb diff --git a/plugins/pushes/local-exec/config.rb b/plugins/pushes/local-exec/config.rb new file mode 100644 index 000000000..eb06a72fe --- /dev/null +++ b/plugins/pushes/local-exec/config.rb @@ -0,0 +1,37 @@ +module VagrantPlugins + module LocalExecPush + class Config < Vagrant.plugin("2", :config) + # The command (as a string) to execute. + # @return [String] + attr_accessor :command + + def initialize + @command = UNSET_VALUE + end + + def finalize! + @command = nil if @command == UNSET_VALUE + end + + def validate(machine) + errors = _detected_errors + + if missing?(@command) + errors << I18n.t("local_exec_push.errors.missing_attribute", + attribute: "command", + ) + end + + { "Local Exec push" => errors } + end + + private + + # Determine if the given string is "missing" (blank) + # @return [true, false] + def missing?(obj) + obj.to_s.strip.empty? + end + end + end +end diff --git a/plugins/pushes/local-exec/errors.rb b/plugins/pushes/local-exec/errors.rb new file mode 100644 index 000000000..5e5b71cca --- /dev/null +++ b/plugins/pushes/local-exec/errors.rb @@ -0,0 +1,13 @@ +module VagrantPlugins + module LocalExecPush + module Errors + class Error < Vagrant::Errors::VagrantError + error_namespace("local_exec_push.errors") + end + + class CommandFailed < Error + error_key(:command_failed) + end + end + end +end diff --git a/plugins/pushes/local-exec/locales/en.yml b/plugins/pushes/local-exec/locales/en.yml new file mode 100644 index 000000000..bd219c8ff --- /dev/null +++ b/plugins/pushes/local-exec/locales/en.yml @@ -0,0 +1,18 @@ +en: + local_exec_push: + errors: + command_failed: |- + The following command exited with a non-zero exit status: + + %{cmd} + + stdout: %{stdout} + stderr: %{stderr} + missing_attribute: |- + Missing required attribute '%{attribute}'. The Vagrant Local Exec Push + plugin requires you set this attribute. Please set this attribute in + your Vagrantfile, for example: + + config.push.define "local-exec" do |push| + push.%{attribute} = "..." + end diff --git a/plugins/pushes/local-exec/plugin.rb b/plugins/pushes/local-exec/plugin.rb new file mode 100644 index 000000000..9f8be467b --- /dev/null +++ b/plugins/pushes/local-exec/plugin.rb @@ -0,0 +1,33 @@ +require "vagrant" + +module VagrantPlugins + module LocalExecPush + class Plugin < Vagrant.plugin("2") + name "local-exec" + description <<-DESC + Run a local command or script to push + DESC + + config(:local_exec, :push) do + require File.expand_path("../config", __FILE__) + init! + Config + end + + push(:local_exec) do + require File.expand_path("../push", __FILE__) + init! + Push + end + + protected + + def self.init! + return if defined?(@_init) + I18n.load_path << File.expand_path("../locales/en.yml", __FILE__) + I18n.reload! + @_init = true + end + end + end +end diff --git a/plugins/pushes/local-exec/push.rb b/plugins/pushes/local-exec/push.rb new file mode 100644 index 000000000..cbcab39e7 --- /dev/null +++ b/plugins/pushes/local-exec/push.rb @@ -0,0 +1,28 @@ +require "vagrant/util/subprocess" + +require_relative "errors" + +module VagrantPlugins + module LocalExecPush + class Push < Vagrant.plugin("2", :push) + def push + execute!(config.command) + end + + # Execute the command, raising an exception if it fails. + # @return [Vagrant::Util::Subprocess::Result] + def execute!(*cmd) + result = Vagrant::Util::Subprocess.execute(*cmd) + + if result.exit_code != 0 + raise Errors::CommandFailed, + cmd: cmd.join(" "), + stdout: result.stdout, + stderr: result.stderr + end + + result + end + end + end +end diff --git a/test/unit/plugins/pushes/local-exec/config_test.rb b/test/unit/plugins/pushes/local-exec/config_test.rb new file mode 100644 index 000000000..84cc9d7b4 --- /dev/null +++ b/test/unit/plugins/pushes/local-exec/config_test.rb @@ -0,0 +1,45 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/pushes/local-exec/config") + +describe VagrantPlugins::LocalExecPush::Config do + include_context "unit" + + before(:all) do + I18n.load_path << Vagrant.source_root.join("plugins/pushes/local-exec/locales/en.yml") + I18n.reload! + end + + let(:machine) { double("machine") } + + describe "#command" do + it "defaults to nil" do + subject.finalize! + expect(subject.command).to be(nil) + end + end + + describe "#validate" do + before do + allow(machine).to receive(:env) + .and_return(double("env", + root_path: "", + )) + + subject.command = "echo" + end + + let(:result) { subject.validate(machine) } + let(:errors) { result["Local Exec push"] } + + context "when the command is missing" do + it "returns an error" do + subject.command = "" + subject.finalize! + expect(errors).to include(I18n.t("local_exec_push.errors.missing_attribute", + attribute: "command", + )) + end + end + end +end diff --git a/test/unit/plugins/pushes/local-exec/push_test.rb b/test/unit/plugins/pushes/local-exec/push_test.rb new file mode 100644 index 000000000..23f487546 --- /dev/null +++ b/test/unit/plugins/pushes/local-exec/push_test.rb @@ -0,0 +1,78 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/pushes/local-exec/push") + +describe VagrantPlugins::LocalExecPush::Push do + include_context "unit" + + before(:all) do + I18n.load_path << Vagrant.source_root.join("plugins/pushes/local-exec/locales/en.yml") + I18n.reload! + end + + let(:env) { isolated_environment } + let(:config) do + double("config", + command: "echo", + ) + end + + subject { described_class.new(env, config) } + + describe "#push" do + before do + allow(subject).to receive(:execute!) + end + + it "executes the command" do + expect(subject).to receive(:execute!) + .with(config.command) + subject.push + end + end + + describe "#execute!" do + let(:exit_code) { 0 } + let(:stdout) { "This is the output" } + let(:stderr) { "This is the errput" } + + let(:process) do + double("process", + exit_code: exit_code, + stdout: stdout, + stderr: stderr, + ) + end + + before do + allow(Vagrant::Util::Subprocess).to receive(:execute) + .and_return(process) + end + + it "creates a subprocess" do + expect(Vagrant::Util::Subprocess).to receive(:execute) + expect { subject.execute! }.to_not raise_error + end + + it "returns the resulting process" do + expect(subject.execute!).to be(process) + end + + context "when the exit code is non-zero" do + let(:exit_code) { 1 } + + it "raises an exception" do + klass = VagrantPlugins::LocalExecPush::Errors::CommandFailed + cmd = ["foo", "bar"] + + expect { subject.execute!(*cmd) }.to raise_error(klass) { |error| + expect(error.message).to eq(I18n.t("local_exec_push.errors.command_failed", + cmd: cmd.join(" "), + stdout: stdout, + stderr: stderr, + )) + } + end + end + end +end From 24595cb606f73380ad69be1b0a5496ab4a5bc614 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Fri, 14 Nov 2014 15:51:40 -0500 Subject: [PATCH 62/83] Add docs for local-exec --- website/docs/source/layouts/layout.erb | 5 +- .../docs/source/v2/push/local-exec.html.md | 46 +++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 website/docs/source/v2/push/local-exec.html.md diff --git a/website/docs/source/layouts/layout.erb b/website/docs/source/layouts/layout.erb index 315408de5..8a1883920 100644 --- a/website/docs/source/layouts/layout.erb +++ b/website/docs/source/layouts/layout.erb @@ -293,8 +293,9 @@ <% if sidebar_section == "push" %> <% end %> diff --git a/website/docs/source/v2/push/local-exec.html.md b/website/docs/source/v2/push/local-exec.html.md new file mode 100644 index 000000000..4c6c64d47 --- /dev/null +++ b/website/docs/source/v2/push/local-exec.html.md @@ -0,0 +1,46 @@ +--- +page_title: "Vagrant Push - Local Exec Strategy" +sidebar_current: "push-local-exec" +description: |- + The Vagrant Push Heroku strategy pushes your application's code to Heroku. + Only files which are committed to the Git repository are pushed to Heroku. +--- + +# Vagrant Push + +## Local Exec Strategy + +The Vagrant Push Local Exec strategy allows the user to invoke an arbitrary +shell command or script as part of a push. + +
+

+ Warning: The Vagrant Push Local Exec strategy does not + perform any validation on the correctness of the shell script. +

+
+ +The Vagrant Push Local Exec strategy supports the following configuration +options: + +- `command` - The command to execute (as a string). + + +### Usage + +The Vagrant Push Local Exec strategy is defined in the `Vagrantfile` using the +`local-exec` key: + +```ruby +config.push.define "local-exec" do |push| + push.command = <<-SCRIPT + scp . /var/www/website + SCRIPT +end +``` + +And then invoke the push with Vagrant: + +```shell +$ vagrant push +``` From fb53f6f3f2bd47eaee8cf96f3b7b650436b01691 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Fri, 14 Nov 2014 15:53:47 -0500 Subject: [PATCH 63/83] Fix description for local-exec docs --- website/docs/source/v2/push/local-exec.html.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/source/v2/push/local-exec.html.md b/website/docs/source/v2/push/local-exec.html.md index 4c6c64d47..b5eb0d487 100644 --- a/website/docs/source/v2/push/local-exec.html.md +++ b/website/docs/source/v2/push/local-exec.html.md @@ -2,8 +2,8 @@ page_title: "Vagrant Push - Local Exec Strategy" sidebar_current: "push-local-exec" description: |- - The Vagrant Push Heroku strategy pushes your application's code to Heroku. - Only files which are committed to the Git repository are pushed to Heroku. + The Vagrant Push Local Exec strategy pushes your application's code using a + user-defined script. --- # Vagrant Push From aa60fe60318cc13a828b0894fe697b3879f77f97 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Fri, 14 Nov 2014 15:55:14 -0500 Subject: [PATCH 64/83] Add example of reading the file --- website/docs/source/v2/push/local-exec.html.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/website/docs/source/v2/push/local-exec.html.md b/website/docs/source/v2/push/local-exec.html.md index b5eb0d487..4e3727f85 100644 --- a/website/docs/source/v2/push/local-exec.html.md +++ b/website/docs/source/v2/push/local-exec.html.md @@ -39,6 +39,15 @@ config.push.define "local-exec" do |push| end ``` +For more complicated scripts, you may store them in a separate file and read +them from the `Vagrantfile` like so: + +```ruby +config.push.define "local-exec" do |push| + push.command = File.read("my-script.sh") +end +``` + And then invoke the push with Vagrant: ```shell From ede14d7daa03afa59833c37461e903186a81d04a Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Mon, 17 Nov 2014 13:16:39 -0500 Subject: [PATCH 65/83] Separate local-exec push `script` from `inline` --- plugins/pushes/local-exec/config.rb | 21 ++++-- plugins/pushes/local-exec/locales/en.yml | 4 ++ plugins/pushes/local-exec/push.rb | 32 ++++++++- .../plugins/pushes/local-exec/config_test.rb | 62 ++++++++++++++--- .../plugins/pushes/local-exec/push_test.rb | 69 +++++++++++++++++-- 5 files changed, 165 insertions(+), 23 deletions(-) diff --git a/plugins/pushes/local-exec/config.rb b/plugins/pushes/local-exec/config.rb index eb06a72fe..747ff8925 100644 --- a/plugins/pushes/local-exec/config.rb +++ b/plugins/pushes/local-exec/config.rb @@ -1,27 +1,38 @@ module VagrantPlugins module LocalExecPush class Config < Vagrant.plugin("2", :config) + # The path (relative to the machine root) to a local script that will be + # executed. + # @return [String] + attr_accessor :script + # The command (as a string) to execute. # @return [String] - attr_accessor :command + attr_accessor :inline def initialize - @command = UNSET_VALUE + @script = UNSET_VALUE + @inline = UNSET_VALUE end def finalize! - @command = nil if @command == UNSET_VALUE + @script = nil if @script == UNSET_VALUE + @inline = nil if @inline == UNSET_VALUE end def validate(machine) errors = _detected_errors - if missing?(@command) + if missing?(@script) && missing?(@inline) errors << I18n.t("local_exec_push.errors.missing_attribute", - attribute: "command", + attribute: "script", ) end + if !missing?(@script) && !missing?(@inline) + errors << I18n.t("local_exec_push.errors.cannot_specify_script_and_inline") + end + { "Local Exec push" => errors } end diff --git a/plugins/pushes/local-exec/locales/en.yml b/plugins/pushes/local-exec/locales/en.yml index bd219c8ff..08808a879 100644 --- a/plugins/pushes/local-exec/locales/en.yml +++ b/plugins/pushes/local-exec/locales/en.yml @@ -1,6 +1,10 @@ en: local_exec_push: errors: + cannot_specify_script_and_inline: |- + You have specified both the 'script' and 'inline' attributes for the + Vagrant Local Exec Push plugin. You may only specify one of these + attributes. command_failed: |- The following command exited with a non-zero exit status: diff --git a/plugins/pushes/local-exec/push.rb b/plugins/pushes/local-exec/push.rb index cbcab39e7..be74e68d5 100644 --- a/plugins/pushes/local-exec/push.rb +++ b/plugins/pushes/local-exec/push.rb @@ -1,3 +1,5 @@ +require "fileutils" +require "tempfile" require "vagrant/util/subprocess" require_relative "errors" @@ -6,11 +8,35 @@ module VagrantPlugins module LocalExecPush class Push < Vagrant.plugin("2", :push) def push - execute!(config.command) + if config.inline + execute_inline!(config.inline) + else + execute_script!(config.script) + end end - # Execute the command, raising an exception if it fails. - # @return [Vagrant::Util::Subprocess::Result] + # Execute the inline script by writing it to a tempfile and executing. + def execute_inline!(inline) + script = Tempfile.new(["vagrant-local-exec-script", ".sh"]) + script.write(inline) + script.rewind + + execute_script!(script.path) + ensure + if script + script.close + script.unlink + end + end + + # Execute the script, expanding the path relative to the current env root. + def execute_script!(path) + path = File.expand_path(path, env.root_path) + FileUtils.chmod("+x", path) + execute!(path) + end + + # Execute the script, raising an exception if it fails. def execute!(*cmd) result = Vagrant::Util::Subprocess.execute(*cmd) diff --git a/test/unit/plugins/pushes/local-exec/config_test.rb b/test/unit/plugins/pushes/local-exec/config_test.rb index 84cc9d7b4..045872d2f 100644 --- a/test/unit/plugins/pushes/local-exec/config_test.rb +++ b/test/unit/plugins/pushes/local-exec/config_test.rb @@ -12,10 +12,17 @@ describe VagrantPlugins::LocalExecPush::Config do let(:machine) { double("machine") } - describe "#command" do + describe "#script" do it "defaults to nil" do subject.finalize! - expect(subject.command).to be(nil) + expect(subject.script).to be(nil) + end + end + + describe "#inline" do + it "defaults to nil" do + subject.finalize! + expect(subject.inline).to be(nil) end end @@ -25,20 +32,53 @@ describe VagrantPlugins::LocalExecPush::Config do .and_return(double("env", root_path: "", )) - - subject.command = "echo" + subject.finalize! end let(:result) { subject.validate(machine) } let(:errors) { result["Local Exec push"] } - context "when the command is missing" do - it "returns an error" do - subject.command = "" - subject.finalize! - expect(errors).to include(I18n.t("local_exec_push.errors.missing_attribute", - attribute: "command", - )) + context "when script is present" do + before { subject.script = "foo.sh" } + + context "when inline is present" do + before { subject.inline = "echo" } + + it "returns an error" do + expect(errors).to include( + I18n.t("local_exec_push.errors.cannot_specify_script_and_inline") + ) + end + end + + context "when inline is not present" do + before { subject.inline = "" } + + it "does not return an error" do + expect(errors).to be_empty + end + end + end + + context "when script is not present" do + before { subject.script = "" } + + context "when inline is present" do + before { subject.inline = "echo" } + + it "does not return an error" do + expect(errors).to be_empty + end + end + + context "when inline is not present" do + before { subject.inline = "" } + + it "returns an error" do + expect(errors).to include(I18n.t("local_exec_push.errors.missing_attribute", + attribute: "script", + )) + end end end end diff --git a/test/unit/plugins/pushes/local-exec/push_test.rb b/test/unit/plugins/pushes/local-exec/push_test.rb index 23f487546..6896428d1 100644 --- a/test/unit/plugins/pushes/local-exec/push_test.rb +++ b/test/unit/plugins/pushes/local-exec/push_test.rb @@ -13,21 +13,82 @@ describe VagrantPlugins::LocalExecPush::Push do let(:env) { isolated_environment } let(:config) do double("config", - command: "echo", + script: nil, + inline: nil, ) end subject { described_class.new(env, config) } + before do + allow(env).to receive(:root_path) + .and_return(File.expand_path("..", __FILE__)) + end + describe "#push" do before do + allow(subject).to receive(:execute_inline!) + allow(subject).to receive(:execute_script!) allow(subject).to receive(:execute!) end - it "executes the command" do + context "when inline is given" do + before { allow(config).to receive(:inline).and_return("echo") } + + it "executes the inline script" do + expect(subject).to receive(:execute_inline!) + .with(config.inline) + subject.push + end + end + + context "when script is given" do + before { allow(config).to receive(:script).and_return("foo.sh") } + + it "executes the script" do + expect(subject).to receive(:execute_script!) + .with(config.script) + subject.push + end + end + end + + describe "#execute_inline!" do + before { allow(subject).to receive(:execute_script!) } + + it "writes the script to a tempfile" do + expect(Tempfile).to receive(:new).and_call_original + subject.execute_inline!("echo") + end + + it "executes the script" do + expect(subject).to receive(:execute_script!) + subject.execute_inline!("echo") + end + end + + describe "#execute_script!" do + before do + allow(subject).to receive(:execute!) + allow(FileUtils).to receive(:chmod) + end + + it "expands the path relative to the machine root" do expect(subject).to receive(:execute!) - .with(config.command) - subject.push + .with(File.expand_path("foo.sh", env.root_path)) + subject.execute_script!("./foo.sh") + end + + it "makes the file executable" do + expect(FileUtils).to receive(:chmod) + .with("+x", File.expand_path("foo.sh", env.root_path)) + subject.execute_script!("./foo.sh") + end + + it "calls execute!" do + expect(subject).to receive(:execute!) + .with(File.expand_path("foo.sh", env.root_path)) + subject.execute_script!("./foo.sh") end end From 202326875f866cfe3df7e9a0769638e1c8f64e8e Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Mon, 17 Nov 2014 13:22:40 -0500 Subject: [PATCH 66/83] Update docs for local-exec push --- website/docs/source/v2/push/local-exec.html.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/website/docs/source/v2/push/local-exec.html.md b/website/docs/source/v2/push/local-exec.html.md index 4e3727f85..536a5eab9 100644 --- a/website/docs/source/v2/push/local-exec.html.md +++ b/website/docs/source/v2/push/local-exec.html.md @@ -23,8 +23,13 @@ shell command or script as part of a push. The Vagrant Push Local Exec strategy supports the following configuration options: -- `command` - The command to execute (as a string). +- `script` - The path to a script on disk (relative to the `Vagrantfile`) to + execute. Vagrant will attempt to convert this script to an executable, but an + exception will be raised if that fails. +- `inline` - The inline script to execute (as a string). +Please note - only one of the `script` and `inline` options may be specified in +a single push definition. ### Usage @@ -33,7 +38,7 @@ The Vagrant Push Local Exec strategy is defined in the `Vagrantfile` using the ```ruby config.push.define "local-exec" do |push| - push.command = <<-SCRIPT + push.inline = <<-SCRIPT scp . /var/www/website SCRIPT end @@ -44,7 +49,7 @@ them from the `Vagrantfile` like so: ```ruby config.push.define "local-exec" do |push| - push.command = File.read("my-script.sh") + push.script = "my-script.sh" end ``` From 998c5688e8a5bab24bfac078b2a28a264941533e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 1 Dec 2014 21:52:03 -0800 Subject: [PATCH 67/83] pushes/atlas: Look for the uploader bin in the embedded dir --- plugins/pushes/atlas/push.rb | 8 +++--- test/unit/plugins/pushes/atlas/push_test.rb | 28 +++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/plugins/pushes/atlas/push.rb b/plugins/pushes/atlas/push.rb index 7dd64a358..0dc90f725 100644 --- a/plugins/pushes/atlas/push.rb +++ b/plugins/pushes/atlas/push.rb @@ -43,10 +43,12 @@ module VagrantPlugins end if Vagrant.in_installer? - # TODO: look up uploader in embedded dir - else - return Vagrant::Util::Which.which(UPLOADER_BIN) + path = File.join( + Vagrant.installer_embedded_dir, "bin", UPLOADER_BIN) + return path if File.file?(path) end + + return Vagrant::Util::Which.which(UPLOADER_BIN) end end end diff --git a/test/unit/plugins/pushes/atlas/push_test.rb b/test/unit/plugins/pushes/atlas/push_test.rb index 40bb9b8d1..2073e5789 100644 --- a/test/unit/plugins/pushes/atlas/push_test.rb +++ b/test/unit/plugins/pushes/atlas/push_test.rb @@ -6,6 +6,8 @@ require Vagrant.source_root.join("plugins/pushes/atlas/push") describe VagrantPlugins::AtlasPush::Push do include_context "unit" + let(:bin) { VagrantPlugins::AtlasPush::Push::UPLOADER_BIN } + let(:env) do double("env", root_path: File.expand_path("..", __FILE__) @@ -99,6 +101,32 @@ describe VagrantPlugins::AtlasPush::Push do expect(subject.uploader_path).to eq("bar") end + it "should look up the uploader in the embedded dir if installer" do + dir = temporary_dir + + allow(Vagrant).to receive(:in_installer?).and_return(true) + allow(Vagrant).to receive(:installer_embedded_dir).and_return(dir.to_s) + + bin_path = dir.join("bin", bin) + bin_path.dirname.mkpath + bin_path.open("w+") { |f| f.write("hi") } + + expect(subject.uploader_path).to eq(bin_path.to_s) + end + + it "should look up the uploader in the PATH if not in the installer" do + dir = temporary_dir + + allow(Vagrant).to receive(:in_installer?).and_return(true) + allow(Vagrant).to receive(:installer_embedded_dir).and_return(dir.to_s) + + expect(Vagrant::Util::Which).to receive(:which). + with(described_class.const_get(:UPLOADER_BIN)). + and_return("bar") + + expect(subject.uploader_path).to eq("bar") + end + it "should return nil if its not found anywhere" do allow(Vagrant).to receive(:in_installer?).and_return(false) allow(Vagrant::Util::Which).to receive(:which).and_return(nil) From 44e6ec6df8cfcda17a964e5169450a19e74fd087 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 1 Dec 2014 22:05:13 -0800 Subject: [PATCH 68/83] pushes/atlas: support custom server address --- plugins/pushes/atlas/config.rb | 8 ++++++++ plugins/pushes/atlas/push.rb | 1 + test/unit/plugins/pushes/atlas/push_test.rb | 8 ++++++++ 3 files changed, 17 insertions(+) diff --git a/plugins/pushes/atlas/config.rb b/plugins/pushes/atlas/config.rb index 71b40e299..9941da455 100644 --- a/plugins/pushes/atlas/config.rb +++ b/plugins/pushes/atlas/config.rb @@ -41,10 +41,17 @@ module VagrantPlugins # @return [String] attr_accessor :uploader_path + # The address of the Atlas server to upload to. By default this will + # be the public Atlas server. + # + # @return [String] + attr_accessor :address + def initialize @app = UNSET_VALUE @dir = UNSET_VALUE @vcs = UNSET_VALUE + @address = UNSET_VALUE @includes = [] @excludes = [] @uploader_path = UNSET_VALUE @@ -58,6 +65,7 @@ module VagrantPlugins end def finalize! + @address = nil if @address == UNSET_VALUE @app = nil if @app == UNSET_VALUE @dir = "." if @dir == UNSET_VALUE @uploader_path = nil if @uploader_path == UNSET_VALUE diff --git a/plugins/pushes/atlas/push.rb b/plugins/pushes/atlas/push.rb index 0dc90f725..0b13f1bb3 100644 --- a/plugins/pushes/atlas/push.rb +++ b/plugins/pushes/atlas/push.rb @@ -26,6 +26,7 @@ module VagrantPlugins cmd << "-vcs" if config.vcs cmd += config.includes.map { |v| ["-include", v] } cmd += config.excludes.map { |v| ["-exclude", v] } + cmd += ["-address", config.address] if config.address cmd << config.app cmd << File.expand_path(config.dir, env.root_path) Vagrant::Util::SafeExec.exec(uploader, *cmd.flatten) diff --git a/test/unit/plugins/pushes/atlas/push_test.rb b/test/unit/plugins/pushes/atlas/push_test.rb index 2073e5789..b93231bb1 100644 --- a/test/unit/plugins/pushes/atlas/push_test.rb +++ b/test/unit/plugins/pushes/atlas/push_test.rb @@ -83,6 +83,14 @@ describe VagrantPlugins::AtlasPush::Push do config.excludes = ["foo", "bar"] subject.execute("foo") end + + it "sends custom server address" do + expect(Vagrant::Util::SafeExec).to receive(:exec). + with("foo", "-vcs", "-address", "foo", app, env.root_path.to_s) + + config.address = "foo" + subject.execute("foo") + end end describe "#uploader_path" do From 4a64da5663796f5c2245da6835f6493082d8c039 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 1 Dec 2014 22:20:27 -0800 Subject: [PATCH 69/83] Fix some issues around push, fix tests, add missing translations --- plugins/commands/push/command.rb | 2 +- plugins/pushes/atlas/errors.rb | 2 +- plugins/pushes/atlas/locales/en.yml | 6 ++++++ test/unit/plugins/commands/push/command_test.rb | 6 +++--- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/plugins/commands/push/command.rb b/plugins/commands/push/command.rb index dc02f7ba7..398e4e279 100644 --- a/plugins/commands/push/command.rb +++ b/plugins/commands/push/command.rb @@ -49,7 +49,7 @@ module VagrantPlugins raise Vagrant::Errors::PushStrategyNotProvided, pushes: pushes end else - if !pushes.key?(name.to_sym) + if !pushes.include?(name.to_sym) raise Vagrant::Errors::PushStrategyNotDefined, name: name, pushes: pushes diff --git a/plugins/pushes/atlas/errors.rb b/plugins/pushes/atlas/errors.rb index 6fee42698..7ba1d712a 100644 --- a/plugins/pushes/atlas/errors.rb +++ b/plugins/pushes/atlas/errors.rb @@ -6,7 +6,7 @@ module VagrantPlugins end class UploaderNotFound < Error - error_key(:uploader_error) + error_key(:uploader_not_found) end end end diff --git a/plugins/pushes/atlas/locales/en.yml b/plugins/pushes/atlas/locales/en.yml index 745e33bd2..c54d59e67 100644 --- a/plugins/pushes/atlas/locales/en.yml +++ b/plugins/pushes/atlas/locales/en.yml @@ -9,3 +9,9 @@ en: config.push.define "atlas" do |push| push.%{attribute} = "..." end + uploader_not_found: |- + Vagrant was unable to find the Atlas uploader CLI. If your Vagrantfile + specifies the path explicitly with "uploader_path", then make sure that + path is valid. Otherwise, make sure that you have a valid install of + Vagrant. If you installed Vagrant outside of the official installers, + the "atlas-upload" binary must exist on your PATH. diff --git a/test/unit/plugins/commands/push/command_test.rb b/test/unit/plugins/commands/push/command_test.rb index 85be81766..18998e87a 100644 --- a/test/unit/plugins/commands/push/command_test.rb +++ b/test/unit/plugins/commands/push/command_test.rb @@ -42,7 +42,7 @@ describe VagrantPlugins::CommandPush::Command do describe "#validate_pushes!" do context "when there are no pushes defined" do - let(:pushes) { {} } + let(:pushes) { [] } context "when a strategy is given" do it "raises an exception" do @@ -61,7 +61,7 @@ describe VagrantPlugins::CommandPush::Command do context "when there is one push defined" do let(:noop) { double("noop") } - let(:pushes) { { noop: noop } } + let(:pushes) { [:noop] } context "when a strategy is given" do context "when that strategy is not defined" do @@ -90,7 +90,7 @@ describe VagrantPlugins::CommandPush::Command do context "when there are multiple pushes defined" do let(:noop) { double("noop") } let(:ftp) { double("ftp") } - let(:pushes) { { noop: noop, ftp: ftp } } + let(:pushes) { [:noop, :ftp] } context "when a strategy is given" do context "when that strategy is not defined" do From 35dbb8db7dc151c15f13380ce47bd9def565e9a6 Mon Sep 17 00:00:00 2001 From: Josh Frye Date: Tue, 2 Dec 2014 12:10:12 -0500 Subject: [PATCH 70/83] Typo --- website/docs/source/v2/push/atlas.html.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/source/v2/push/atlas.html.md b/website/docs/source/v2/push/atlas.html.md index 0d6347345..f001cfa51 100644 --- a/website/docs/source/v2/push/atlas.html.md +++ b/website/docs/source/v2/push/atlas.html.md @@ -32,7 +32,7 @@ The Vagrant Push Atlas strategy supports the following configuration options: the same directory as the Vagrantfile, but you can specify this if you have a `src` folder or `bin` folder or some other folder you want to upload. -- `vsc` - If set to true, Vagrant will automatically use VCS data to determine +- `vcs` - If set to true, Vagrant will automatically use VCS data to determine the files to upload. Uncommitted changes will not be deployed. From 6b48199346bd73d2c7d9a8b4f32b54fe00b1b045 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 2 Dec 2014 13:30:16 -0500 Subject: [PATCH 71/83] Infer push name when only one strategy is defined, support multiple strategies --- lib/vagrant/environment.rb | 2 +- plugins/commands/push/command.rb | 59 +++++++++++------- .../plugins/commands/push/command_test.rb | 61 +++++++++++++++---- website/docs/source/v2/push/index.html.md | 10 +++ 4 files changed, 98 insertions(+), 34 deletions(-) diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 82cab0411..acda0c178 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -571,7 +571,7 @@ module Vagrant # # @return [Array] def pushes - vagrantfile.config.push.__compiled_pushes.keys + self.vagrantfile.config.push.__compiled_pushes.keys end # This returns a machine with the proper provider for this environment. diff --git a/plugins/commands/push/command.rb b/plugins/commands/push/command.rb index 398e4e279..f3390723b 100644 --- a/plugins/commands/push/command.rb +++ b/plugins/commands/push/command.rb @@ -7,56 +7,73 @@ module VagrantPlugins "deploys code in this environment to a configured destination" end - # @todo support multiple strategies if requested by the community def execute + options = { all: false } opts = OptionParser.new do |o| o.banner = "Usage: vagrant push [strategy] [options]" + o.on("-a", "--all", "Run all defined push strategies") do + options[:all] = true + end end # Parse the options argv = parse_options(opts) return if !argv - name = argv[0] - pushes = @env.pushes + names = validate_pushes!(@env.pushes, argv, options) - validate_pushes!(pushes, name) - - @logger.debug("'push' environment with strategy: `#{name}'") - @env.push(name) + names.each do |name| + @logger.debug("'push' environment with strategy: `#{name}'") + @env.push(name) + end 0 end - # Validate that the given list of pushes and strategy are valid. + # Validate that the given list of names corresponds to valid pushes. # - # @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 + # @raise Vagrant::Errors::PushesNotDefined + # if there are no pushes defined + # @raise Vagrant::Errors::PushStrategyNotProvided + # if there are multiple push strategies defined and none were specified + # and `--all` was not given + # @raise Vagrant::Errors::PushStrategyNotDefined + # if any of the given push names do not correspond to a push strategy # - # @param [Registry] pushes The list of pushes as a {Registry} - # @param [#to_sym, nil] name The name of the strategy + # @param [Array] pushes + # the list of pushes defined by the environment + # @param [Array] names + # the list of names provided by the user on the command line + # @param [Hash] options + # a list of options to pass to the validation # - # @return [true] - def validate_pushes!(pushes, name=nil) + # @return [Array] + # the compiled list of pushes + # + def validate_pushes!(pushes, names = [], options = {}) if pushes.nil? || pushes.empty? raise Vagrant::Errors::PushesNotDefined end - if name.nil? - if pushes.length != 1 + names = Array(names).flatten.compact.map(&:to_sym) + + if names.empty? || options[:all] + if options[:all] || pushes.length == 1 + return pushes.map(&:to_sym) + else raise Vagrant::Errors::PushStrategyNotProvided, pushes: pushes end - else - if !pushes.include?(name.to_sym) + end + + names.each do |name| + if !pushes.include?(name) raise Vagrant::Errors::PushStrategyNotDefined, name: name, pushes: pushes end end - true + return names end end end diff --git a/test/unit/plugins/commands/push/command_test.rb b/test/unit/plugins/commands/push/command_test.rb index 18998e87a..292e2ee0e 100644 --- a/test/unit/plugins/commands/push/command_test.rb +++ b/test/unit/plugins/commands/push/command_test.rb @@ -25,15 +25,35 @@ describe VagrantPlugins::CommandPush::Command do describe "#execute" do before do allow(subject).to receive(:validate_pushes!) + .and_return([:noop]) allow(env).to receive(:pushes) allow(env).to receive(:push) end it "validates the pushes" do - expect(subject).to receive(:validate_pushes!).once + expect(subject).to receive(:validate_pushes!) + .with(nil, argv, kind_of(Hash)) + .once subject.execute end + it "parses arguments" do + list = ["noop", "ftp"] + instance = described_class.new(list, env) + expect(instance).to receive(:validate_pushes!) + .with(nil, list, kind_of(Hash)) + .and_return([]) + instance.execute + end + + it "parses options" do + instance = described_class.new(["--all"], env) + expect(instance).to receive(:validate_pushes!) + .with(nil, argv, all: true) + .and_return([]) + instance.execute + end + it "delegates to Environment#push" do expect(env).to receive(:push).once subject.execute @@ -72,17 +92,22 @@ describe VagrantPlugins::CommandPush::Command do 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 + it "returns that push" do + expect(subject.validate_pushes!(pushes, :noop)).to eq([:noop]) 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 + it "returns the push" do + expect(subject.validate_pushes!(pushes)).to eq([:noop]) + end + end + + context "when --all is given" do + it "returns the push" do + expect(subject.validate_pushes!(pushes, [], all: true)) + .to eq([:noop]) end end end @@ -101,21 +126,33 @@ describe VagrantPlugins::CommandPush::Command do 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 + it "returns the strategy" do + expect(subject.validate_pushes!(pushes, :noop)).to eq([:noop]) + expect(subject.validate_pushes!(pushes, :ftp)).to eq([:ftp]) end end end + context "when multiple strategies are given" do + it "returns the pushes" do + expect(subject.validate_pushes!(pushes, [:noop, :ftp])) + .to eq([:noop, :ftp]) + 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 + + context "when --all is given" do + it "returns the pushes" do + expect(subject.validate_pushes!(pushes, [], all: true)) + .to eq([:noop, :ftp]) + end + end end end end diff --git a/website/docs/source/v2/push/index.html.md b/website/docs/source/v2/push/index.html.md index 310039176..5f061862a 100644 --- a/website/docs/source/v2/push/index.html.md +++ b/website/docs/source/v2/push/index.html.md @@ -52,6 +52,16 @@ subcommand: $ vagrant push staging ``` +Pushes will be run in the order you specify on the command line, **not the order +they are specified in the `Vagrantfile`!** + +To execute all the Vagrant Push strategies, specify the `--all` flag with no +other arguments: + +```shell +$ vagrant push --all +``` + Vagrant Push is the easiest way to deploy your application. You can read more in the documentation links on the sidebar. From 70b61047c748e05b39aa838460d8f697edecdfae Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 2 Dec 2014 13:44:42 -0500 Subject: [PATCH 72/83] Do not support multiple strategies right now --- plugins/commands/push/command.rb | 45 ++++++----------- .../plugins/commands/push/command_test.rb | 50 ++----------------- website/docs/source/v2/push/index.html.md | 10 ---- 3 files changed, 21 insertions(+), 84 deletions(-) diff --git a/plugins/commands/push/command.rb b/plugins/commands/push/command.rb index f3390723b..5fde89175 100644 --- a/plugins/commands/push/command.rb +++ b/plugins/commands/push/command.rb @@ -8,24 +8,18 @@ module VagrantPlugins end def execute - options = { all: false } opts = OptionParser.new do |o| o.banner = "Usage: vagrant push [strategy] [options]" - o.on("-a", "--all", "Run all defined push strategies") do - options[:all] = true - end end # Parse the options argv = parse_options(opts) return if !argv - names = validate_pushes!(@env.pushes, argv, options) + name = validate_pushes!(@env.pushes, argv[0]) - names.each do |name| - @logger.debug("'push' environment with strategy: `#{name}'") - @env.push(name) - end + @logger.debug("'push' environment with strategy: `#{name}'") + @env.push(name) 0 end @@ -36,44 +30,37 @@ module VagrantPlugins # if there are no pushes defined # @raise Vagrant::Errors::PushStrategyNotProvided # if there are multiple push strategies defined and none were specified - # and `--all` was not given # @raise Vagrant::Errors::PushStrategyNotDefined - # if any of the given push names do not correspond to a push strategy + # if the given push name do not correspond to a push strategy # # @param [Array] pushes # the list of pushes defined by the environment - # @param [Array] names - # the list of names provided by the user on the command line - # @param [Hash] options - # a list of options to pass to the validation + # @param [String] name + # the name provided by the user on the command line # - # @return [Array] + # @return [Symbol] # the compiled list of pushes # - def validate_pushes!(pushes, names = [], options = {}) + def validate_pushes!(pushes, name = nil) if pushes.nil? || pushes.empty? raise Vagrant::Errors::PushesNotDefined end - names = Array(names).flatten.compact.map(&:to_sym) - - if names.empty? || options[:all] - if options[:all] || pushes.length == 1 - return pushes.map(&:to_sym) + if name.nil? + if pushes.length == 1 + return pushes.first.to_sym else raise Vagrant::Errors::PushStrategyNotProvided, pushes: pushes end end - names.each do |name| - if !pushes.include?(name) - raise Vagrant::Errors::PushStrategyNotDefined, - name: name, - pushes: pushes - end + if !pushes.include?(name) + raise Vagrant::Errors::PushStrategyNotDefined, + name: name, + pushes: pushes end - return names + return name.to_sym end end end diff --git a/test/unit/plugins/commands/push/command_test.rb b/test/unit/plugins/commands/push/command_test.rb index 292e2ee0e..113908ebf 100644 --- a/test/unit/plugins/commands/push/command_test.rb +++ b/test/unit/plugins/commands/push/command_test.rb @@ -31,29 +31,10 @@ describe VagrantPlugins::CommandPush::Command do end it "validates the pushes" do - expect(subject).to receive(:validate_pushes!) - .with(nil, argv, kind_of(Hash)) - .once + expect(subject).to receive(:validate_pushes!).once subject.execute end - it "parses arguments" do - list = ["noop", "ftp"] - instance = described_class.new(list, env) - expect(instance).to receive(:validate_pushes!) - .with(nil, list, kind_of(Hash)) - .and_return([]) - instance.execute - end - - it "parses options" do - instance = described_class.new(["--all"], env) - expect(instance).to receive(:validate_pushes!) - .with(nil, argv, all: true) - .and_return([]) - instance.execute - end - it "delegates to Environment#push" do expect(env).to receive(:push).once subject.execute @@ -93,21 +74,14 @@ describe VagrantPlugins::CommandPush::Command do context "when that strategy is defined" do it "returns that push" do - expect(subject.validate_pushes!(pushes, :noop)).to eq([:noop]) + expect(subject.validate_pushes!(pushes, :noop)).to eq(:noop) end end end context "when no strategy is given" do it "returns the push" do - expect(subject.validate_pushes!(pushes)).to eq([:noop]) - end - end - - context "when --all is given" do - it "returns the push" do - expect(subject.validate_pushes!(pushes, [], all: true)) - .to eq([:noop]) + expect(subject.validate_pushes!(pushes)).to eq(:noop) end end end @@ -127,32 +101,18 @@ describe VagrantPlugins::CommandPush::Command do context "when that strategy is defined" do it "returns the strategy" do - expect(subject.validate_pushes!(pushes, :noop)).to eq([:noop]) - expect(subject.validate_pushes!(pushes, :ftp)).to eq([:ftp]) + expect(subject.validate_pushes!(pushes, :noop)).to eq(:noop) + expect(subject.validate_pushes!(pushes, :ftp)).to eq(:ftp) end end end - context "when multiple strategies are given" do - it "returns the pushes" do - expect(subject.validate_pushes!(pushes, [:noop, :ftp])) - .to eq([:noop, :ftp]) - 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 - - context "when --all is given" do - it "returns the pushes" do - expect(subject.validate_pushes!(pushes, [], all: true)) - .to eq([:noop, :ftp]) - end - end end end end diff --git a/website/docs/source/v2/push/index.html.md b/website/docs/source/v2/push/index.html.md index 5f061862a..310039176 100644 --- a/website/docs/source/v2/push/index.html.md +++ b/website/docs/source/v2/push/index.html.md @@ -52,16 +52,6 @@ subcommand: $ vagrant push staging ``` -Pushes will be run in the order you specify on the command line, **not the order -they are specified in the `Vagrantfile`!** - -To execute all the Vagrant Push strategies, specify the `--all` flag with no -other arguments: - -```shell -$ vagrant push --all -``` - Vagrant Push is the easiest way to deploy your application. You can read more in the documentation links on the sidebar. From 78a4fdd6cd72c04a5f80c92d7684659243af7eca Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 2 Dec 2014 13:45:14 -0500 Subject: [PATCH 73/83] Be consistent --- test/unit/plugins/commands/push/command_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/plugins/commands/push/command_test.rb b/test/unit/plugins/commands/push/command_test.rb index 113908ebf..8adfb2691 100644 --- a/test/unit/plugins/commands/push/command_test.rb +++ b/test/unit/plugins/commands/push/command_test.rb @@ -80,7 +80,7 @@ describe VagrantPlugins::CommandPush::Command do end context "when no strategy is given" do - it "returns the push" do + it "returns the strategy" do expect(subject.validate_pushes!(pushes)).to eq(:noop) end end From 8df0b1848c6bd1c18e7923f2b0e154778f399b91 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 2 Dec 2014 13:45:45 -0500 Subject: [PATCH 74/83] Just return a symbol --- test/unit/plugins/commands/push/command_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/plugins/commands/push/command_test.rb b/test/unit/plugins/commands/push/command_test.rb index 8adfb2691..06995d51d 100644 --- a/test/unit/plugins/commands/push/command_test.rb +++ b/test/unit/plugins/commands/push/command_test.rb @@ -25,7 +25,7 @@ describe VagrantPlugins::CommandPush::Command do describe "#execute" do before do allow(subject).to receive(:validate_pushes!) - .and_return([:noop]) + .and_return(:noop) allow(env).to receive(:pushes) allow(env).to receive(:push) end From 9e5b587e66ee3b59ec8c8fbc30d6f59d931775bf Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 2 Dec 2014 13:46:22 -0500 Subject: [PATCH 75/83] Re-add TODO --- plugins/commands/push/command.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/commands/push/command.rb b/plugins/commands/push/command.rb index 5fde89175..d04e296fb 100644 --- a/plugins/commands/push/command.rb +++ b/plugins/commands/push/command.rb @@ -7,6 +7,7 @@ module VagrantPlugins "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]" From fa7cd37e42216f7dda5a84dc99ba7f2da9bec67f Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Mon, 8 Dec 2014 16:54:19 -0800 Subject: [PATCH 76/83] Send the Atlas token --- plugins/pushes/atlas/config.rb | 49 +++++++++-- plugins/pushes/atlas/locales/en.yml | 5 ++ plugins/pushes/atlas/push.rb | 1 + test/unit/plugins/pushes/atlas/config_test.rb | 87 +++++++++++++++++++ test/unit/plugins/pushes/atlas/push_test.rb | 8 ++ website/docs/source/v2/push/atlas.html.md | 11 +++ 6 files changed, 154 insertions(+), 7 deletions(-) diff --git a/plugins/pushes/atlas/config.rb b/plugins/pushes/atlas/config.rb index 9941da455..5c4a1a7cf 100644 --- a/plugins/pushes/atlas/config.rb +++ b/plugins/pushes/atlas/config.rb @@ -1,6 +1,19 @@ module VagrantPlugins module AtlasPush class Config < Vagrant.plugin("2", :config) + # The address of the Atlas server to upload to. By default this will + # be the public Atlas server. + # + # @return [String] + attr_accessor :address + + # The Atlas token to use. If the user has run `vagrant login`, this will + # use that token. If the environment variable `ATLAS_TOKEN` is set, the + # uploader will use this value. By default, this is nil. + # + # @return [String, nil] + attr_accessor :token + # The name of the application to push to. This will be created (with # user confirmation) if it doesn't already exist. # @@ -41,17 +54,12 @@ module VagrantPlugins # @return [String] attr_accessor :uploader_path - # The address of the Atlas server to upload to. By default this will - # be the public Atlas server. - # - # @return [String] - attr_accessor :address - def initialize + @address = UNSET_VALUE + @token = UNSET_VALUE @app = UNSET_VALUE @dir = UNSET_VALUE @vcs = UNSET_VALUE - @address = UNSET_VALUE @includes = [] @excludes = [] @uploader_path = UNSET_VALUE @@ -66,6 +74,7 @@ module VagrantPlugins def finalize! @address = nil if @address == UNSET_VALUE + @token = nil if @token == UNSET_VALUE @app = nil if @app == UNSET_VALUE @dir = "." if @dir == UNSET_VALUE @uploader_path = nil if @uploader_path == UNSET_VALUE @@ -75,6 +84,15 @@ module VagrantPlugins def validate(machine) errors = _detected_errors + if missing?(@token) + token = token_from_vagrant_login(machine.env) || ENV["ATLAS_TOKEN"] + if missing?(token) + errors << I18n.t("atlas_push.errors.missing_token") + else + @token = token + end + end + if missing?(@app) errors << I18n.t("atlas_push.errors.missing_attribute", attribute: "app", @@ -111,6 +129,23 @@ module VagrantPlugins def missing?(obj) obj.to_s.strip.empty? end + + # Attempt to load the token from disk using the vagrant-login plugin. If + # the constant is not defined, that means the user is operating in some + # bespoke and unsupported Ruby environment. + # + # @param [Vagrant::Environment] env + # + # @return [String, nil] + # the token, or nil if it does not exist + def token_from_vagrant_login(env) + if defined?(VagrantPlugins::Login::Client) + client = VagrantPlugins::Login::Client.new(env) + return client.token + end + + nil + end end end end diff --git a/plugins/pushes/atlas/locales/en.yml b/plugins/pushes/atlas/locales/en.yml index c54d59e67..3377f62dd 100644 --- a/plugins/pushes/atlas/locales/en.yml +++ b/plugins/pushes/atlas/locales/en.yml @@ -9,6 +9,11 @@ en: config.push.define "atlas" do |push| push.%{attribute} = "..." end + missing_token: |- + Missing required configuration parameter 'token'. This is required for + Vagrant to securely communicate with your Atlas account. + + To generate an access token, run 'vagrant login'. uploader_not_found: |- Vagrant was unable to find the Atlas uploader CLI. If your Vagrantfile specifies the path explicitly with "uploader_path", then make sure that diff --git a/plugins/pushes/atlas/push.rb b/plugins/pushes/atlas/push.rb index 0b13f1bb3..24a6d28b0 100644 --- a/plugins/pushes/atlas/push.rb +++ b/plugins/pushes/atlas/push.rb @@ -27,6 +27,7 @@ module VagrantPlugins cmd += config.includes.map { |v| ["-include", v] } cmd += config.excludes.map { |v| ["-exclude", v] } cmd += ["-address", config.address] if config.address + cmd += ["-token", config.token] if config.token cmd << config.app cmd << File.expand_path(config.dir, env.root_path) Vagrant::Util::SafeExec.exec(uploader, *cmd.flatten) diff --git a/test/unit/plugins/pushes/atlas/config_test.rb b/test/unit/plugins/pushes/atlas/config_test.rb index 87411c472..e62a5b86e 100644 --- a/test/unit/plugins/pushes/atlas/config_test.rb +++ b/test/unit/plugins/pushes/atlas/config_test.rb @@ -12,6 +12,20 @@ describe VagrantPlugins::AtlasPush::Config do let(:machine) { double("machine") } + describe "#address" do + it "defaults to nil" do + subject.finalize! + expect(subject.address).to be(nil) + end + end + + describe "#token" do + it "defaults to nil" do + subject.finalize! + expect(subject.token).to be(nil) + end + end + describe "#app" do it "defaults to nil" do subject.finalize! @@ -56,6 +70,79 @@ describe VagrantPlugins::AtlasPush::Config do let(:result) { subject.validate(machine) } let(:errors) { result["Atlas push"] } + context "when the token is missing" do + context "when a vagrant-login token exists" do + before do + allow(subject).to receive(:token_from_vagrant_login) + .and_return("token_from_vagrant_login") + + allow(ENV).to receive(:[]).and_call_original + allow(ENV).to receive(:[]) + .with("ATLAS_TOKEN").and_return("token_from_env") + end + + it "uses the token in the Vagrantfile" do + subject.token = "" + subject.finalize! + expect(errors).to be_empty + expect(subject.token).to eq("token_from_vagrant_login") + end + end + + context "when ATLAS_TOKEN is set in the environment" do + before do + allow(subject).to receive(:token_from_vagrant_login) + .and_return(nil) + + allow(ENV).to receive(:[]).and_call_original + allow(ENV).to receive(:[]) + .with("ATLAS_TOKEN").and_return("token_from_env") + end + + it "uses the token in the environment" do + subject.token = "" + subject.finalize! + expect(errors).to be_empty + expect(subject.token).to eq("token_from_env") + end + end + + context "when a token is given in the Vagrantfile" do + before do + allow(subject).to receive(:token_from_vagrant_login) + .and_return("token_from_vagrant_login") + + allow(ENV).to receive(:[]).and_call_original + allow(ENV).to receive(:[]) + .with("ATLAS_TOKEN").and_return("token_from_env") + end + + it "uses the token in the Vagrantfile" do + subject.token = "token_from_vagrantfile" + subject.finalize! + expect(errors).to be_empty + expect(subject.token).to eq("token_from_vagrantfile") + end + end + + context "when no token is given" do + before do + allow(subject).to receive(:token_from_vagrant_login) + .and_return(nil) + + allow(ENV).to receive(:[]).and_call_original + allow(ENV).to receive(:[]) + .with("ATLAS_TOKEN").and_return(nil) + end + + it "returns an error" do + subject.token = "" + subject.finalize! + expect(errors).to include(I18n.t("atlas_push.errors.missing_token")) + end + end + end + context "when the app is missing" do it "returns an error" do subject.app = "" diff --git a/test/unit/plugins/pushes/atlas/push_test.rb b/test/unit/plugins/pushes/atlas/push_test.rb index b93231bb1..e7ffdebd5 100644 --- a/test/unit/plugins/pushes/atlas/push_test.rb +++ b/test/unit/plugins/pushes/atlas/push_test.rb @@ -91,6 +91,14 @@ describe VagrantPlugins::AtlasPush::Push do config.address = "foo" subject.execute("foo") end + + it "sends custom token" do + expect(Vagrant::Util::SafeExec).to receive(:exec). + with("foo", "-vcs", "-token", "atlas_token", app, env.root_path.to_s) + + config.token = "atlas_token" + subject.execute("foo") + end end describe "#uploader_path" do diff --git a/website/docs/source/v2/push/atlas.html.md b/website/docs/source/v2/push/atlas.html.md index f001cfa51..e5679bbf2 100644 --- a/website/docs/source/v2/push/atlas.html.md +++ b/website/docs/source/v2/push/atlas.html.md @@ -35,6 +35,17 @@ The Vagrant Push Atlas strategy supports the following configuration options: - `vcs` - If set to true, Vagrant will automatically use VCS data to determine the files to upload. Uncommitted changes will not be deployed. +Additionally, the following options are exposed for power users of the Vagrant +Atlas push strategy. Most users will not require these options: + +- `address` - The address of the Atlas server to upload to. By default this will + be the public Atlas server. + +- `token` - The Atlas token to use. If the user has run `vagrant login`, this + will the token generated by that command. If the environment variable + `ATLAS_TOKEN` is set, the uploader will use this value. By default, this is + nil. + ### Usage From 2e4f854725ab915f2051755db87279c06d7611ef Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Mon, 8 Dec 2014 17:42:29 -0800 Subject: [PATCH 77/83] Vagrant Cloud -> Atlas --- lib/vagrant/action/builtin/box_add.rb | 2 +- lib/vagrant/shared_helpers.rb | 4 ++-- plugins/commands/box/command/add.rb | 2 +- templates/locales/en.yml | 20 +++++++++---------- website/docs/source/v2/boxes.html.md | 4 ++-- website/docs/source/v2/boxes/base.html.md | 2 +- website/docs/source/v2/boxes/format.html.md | 6 +++--- .../docs/source/v2/boxes/versioning.html.md | 6 +++--- website/docs/source/v2/cli/box.html.md | 8 ++++---- website/docs/source/v2/cli/login.html.md | 2 +- .../source/v2/getting-started/boxes.html.md | 12 +++++------ .../source/v2/getting-started/share.html.md | 4 ++-- website/docs/source/v2/hyperv/usage.html.md | 2 +- website/docs/source/v2/share/index.html.md | 2 +- .../v2/vagrantfile/machine_settings.html.md | 6 +++--- 15 files changed, 40 insertions(+), 42 deletions(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 554613b94..13688bae5 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -147,7 +147,7 @@ module Vagrant # element is an authenticated URL. # @param [Hash] env # @param [Bool] expanded True if the metadata URL was expanded with - # a Vagrant Cloud server URL. + # a Atlas server URL. def add_from_metadata(url, env, expanded) original_url = env[:box_url] provider = env[:box_provider] diff --git a/lib/vagrant/shared_helpers.rb b/lib/vagrant/shared_helpers.rb index c2c52ae10..b195c36df 100644 --- a/lib/vagrant/shared_helpers.rb +++ b/lib/vagrant/shared_helpers.rb @@ -5,12 +5,12 @@ require "thread" module Vagrant @@global_lock = Mutex.new - # This is the default endpoint of the Vagrant Cloud in + # This is the default endpoint of the Atlas in # use. API calls will be made to this for various functions # of Vagrant that may require remote access. # # @return [String] - DEFAULT_SERVER_URL = "https://vagrantcloud.com" + DEFAULT_SERVER_URL = "https://atlas.hashicorp.com" # This holds a global lock for the duration of the block. This should # be invoked around anything that is modifying process state (such as diff --git a/plugins/commands/box/command/add.rb b/plugins/commands/box/command/add.rb index 0356ee298..d82d441a8 100644 --- a/plugins/commands/box/command/add.rb +++ b/plugins/commands/box/command/add.rb @@ -47,7 +47,7 @@ module VagrantPlugins end o.separator "" - o.separator "The box descriptor can be the name of a box on Vagrant Cloud," + o.separator "The box descriptor can be the name of a box on HashiCorp's Atlas," o.separator "or a URL, or a local .box file, or a local .json file containing" o.separator "the catalog metadata." o.separator "" diff --git a/templates/locales/en.yml b/templates/locales/en.yml index a201a3833..e28106a20 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -369,7 +369,7 @@ en: provider. Double-check your requested provider to verify you didn't simply misspell it. - If you're adding a box from Vagrant Cloud, make sure the box is + If you're adding a box from HashiCorp's Atlas, make sure the box is released. Name: %{name} @@ -388,7 +388,7 @@ en: box_add_short_not_found: |- The box '%{name}' could not be found or could not be accessed in the remote catalog. If this is a private - box on Vagrant Cloud, please verify you're logged in via + box on HashiCorp's Atlas, please verify you're logged in via `vagrant login`. Also, please double-check the name. The expanded URL and error message are shown below: @@ -550,16 +550,14 @@ en: %{versions} box_server_not_set: |- - A URL to a Vagrant Cloud server is not set, so boxes cannot - be added with a shorthand ("mitchellh/precise64") format. - You may also be seeing this error if you meant to type in - a path to a box file which doesn't exist locally on your - system. + A URL to an Atlas server is not set, so boxes cannot be added with a + shorthand ("mitchellh/precise64") format. You may also be seeing this + error if you meant to type in a path to a box file which doesn't exist + locally on your system. - To set a URL to a Vagrant Cloud server, set the - `VAGRANT_SERVER_URL` environmental variable. Or, if you - meant to use a file path, make sure the path to the file - is valid. + To set a URL to an Atlas server, set the `VAGRANT_SERVER_URL` + environmental variable. Or, if you meant to use a file path, make sure + the path to the file is valid. box_update_multi_provider: |- You requested to update the box '%{name}'. This box has multiple providers. You must explicitly select a single diff --git a/website/docs/source/v2/boxes.html.md b/website/docs/source/v2/boxes.html.md index e3c1661de..491a5a631 100644 --- a/website/docs/source/v2/boxes.html.md +++ b/website/docs/source/v2/boxes.html.md @@ -14,7 +14,7 @@ boxes. You can read the documentation on the [vagrant box](/v2/cli/box.html) command for more information. The easiest way to use a box is to add a box from the -[publicly available catalog of Vagrant boxes](https://vagrantcloud.com). +[publicly available catalog of Vagrant boxes](https://atlas.hashicorp.com). You can also add and share your own customized boxes on this website. Boxes also support versioning so that members of your team using Vagrant @@ -27,7 +27,7 @@ sub-pages in the navigation to the left. ## Discovering Boxes The easiest way to find boxes is to look on the -[public Vagrant box catalog](https://vagrantcloud.com) +[public Vagrant box catalog](https://atlas.hashicorp.com) for a box matching your use case. The catalog contains most major operating systems as bases, as well as specialized boxes to get you up and running quickly with LAMP stacks, Ruby, Python, etc. diff --git a/website/docs/source/v2/boxes/base.html.md b/website/docs/source/v2/boxes/base.html.md index e771187a0..61007ce92 100644 --- a/website/docs/source/v2/boxes/base.html.md +++ b/website/docs/source/v2/boxes/base.html.md @@ -239,7 +239,7 @@ provider-specific guides are linked to towards the top of this page. You can distribute the box file however you'd like. However, if you want to support versioning, putting multiple providers at a single URL, pushing updates, analytics, and more, we recommend you add the box to -[Vagrant Cloud](https://vagrantcloud.com). +[HashiCorp's Atlas](https://atlas.hashicorp.com). You can upload both public and private boxes to this service. diff --git a/website/docs/source/v2/boxes/format.html.md b/website/docs/source/v2/boxes/format.html.md index 0eb584026..3fa50479b 100644 --- a/website/docs/source/v2/boxes/format.html.md +++ b/website/docs/source/v2/boxes/format.html.md @@ -23,7 +23,7 @@ Today, there are two different components: box file and so on. * Box Catalog Metadata - This is a JSON document (typically exchanged - during interactions with [Vagrant Cloud](https://vagrantcloud.com)) + during interactions with [HashiCorp's Atlas](https://atlas.hashicorp.com)) that specifies the name of the box, a description, available versions, available providers, and URLs to the actual box files (next component) for each provider and version. If this catalog @@ -78,8 +78,8 @@ providers from a single file, and more.
You don't need to manually make the metadata. If you -have an account with Vagrant Cloud, you -can create boxes there, and Vagrant Cloud automatically creates +have an account with HashiCorp's Atlas, you +can create boxes there, and HashiCorp's Atlas automatically creates the metadata for you. The format is still documented here.
diff --git a/website/docs/source/v2/boxes/versioning.html.md b/website/docs/source/v2/boxes/versioning.html.md index 61af2345a..dc4cf3e28 100644 --- a/website/docs/source/v2/boxes/versioning.html.md +++ b/website/docs/source/v2/boxes/versioning.html.md @@ -24,10 +24,10 @@ to update your own custom boxes with versions. That is covered in `vagrant box list` only shows _installed_ versions of boxes. If you want to see all available versions of a box, you'll have to find the box -on [Vagrant Cloud](https://vagrantcloud.com). An easy way to find a box -is to use the url `https://vagrantcloud.com/USER/BOX`. For example, for +on [HashiCorp's Atlas](https://atlas.hashicorp.com). An easy way to find a box +is to use the url `https://atlas.hashicorp.com/USER/BOX`. For example, for the `hashicorp/precise64` box, you can find information about it at -`https://vagrantcloud.com/hashicorp/precise64`. +`https://atlas.hashicorp.com/hashicorp/precise64`. You can check if the box you're using is outdated with `vagrant box outdated`. This can check if the box in your current Vagrant environment is outdated diff --git a/website/docs/source/v2/cli/box.html.md b/website/docs/source/v2/cli/box.html.md index 0a5cff9fb..e3b39b4cb 100644 --- a/website/docs/source/v2/cli/box.html.md +++ b/website/docs/source/v2/cli/box.html.md @@ -26,10 +26,10 @@ This adds a box with the given address to Vagrant. The address can be one of three things: * A shorthand name from the -[public catalog of available Vagrant images](https://vagrantcloud.com), +[public catalog of available Vagrant images](https://atlas.hashicorp.com), such as "hashicorp/precise64". -* File path or HTTP URL to a box in a [catalog](https://vagrantcloud.com). +* File path or HTTP URL to a box in a [catalog](https://atlas.hashicorp.com). For HTTP, basic authentication is supported and `http_proxy` environmental variables are respected. HTTPS is also supported. @@ -93,8 +93,8 @@ you're not using a catalog). to be specified.
-Checksums for versioned boxes or boxes from Vagrant Cloud: -For boxes from Vagrant Cloud, the checksums are embedded in the metadata +Checksums for versioned boxes or boxes from HashiCorp's Atlas: +For boxes from HashiCorp's Atlas, the checksums are embedded in the metadata of the box. The metadata itself is served over TLS and its format is validated.
diff --git a/website/docs/source/v2/cli/login.html.md b/website/docs/source/v2/cli/login.html.md index 5e2e56065..a984cc77d 100644 --- a/website/docs/source/v2/cli/login.html.md +++ b/website/docs/source/v2/cli/login.html.md @@ -8,7 +8,7 @@ sidebar_current: "cli-login" **Command: `vagrant login`** The login command is used to authenticate with a -[Vagrant Cloud](https://vagrantcloud.com) server. Logging is only +[HashiCorp's Atlas](https://atlas.hashicorp.com) server. Logging is only necessary if you're accessing protected boxes or using [Vagrant Share](/v2/share/index.html). diff --git a/website/docs/source/v2/getting-started/boxes.html.md b/website/docs/source/v2/getting-started/boxes.html.md index b16aa893e..6a64f8a71 100644 --- a/website/docs/source/v2/getting-started/boxes.html.md +++ b/website/docs/source/v2/getting-started/boxes.html.md @@ -27,8 +27,8 @@ $ vagrant box add hashicorp/precise32 ``` This will download the box named "hashicorp/precise32" from -[Vagrant Cloud](https://vagrantcloud.com), a place where you can find -and host boxes. While it is easiest to download boxes from Vagrant Cloud +[HashiCorp's Atlas](https://atlas.hashicorp.com), a place where you can find +and host boxes. While it is easiest to download boxes from HashiCorp's Atlas you can also add boxes from a local file, custom URL, etc. Added boxes can be re-used by multiple projects. Each project uses a box @@ -64,11 +64,11 @@ For the remainder of this getting started guide, we'll only use the this getting started guide, the first question you'll probably have is "where do I find more boxes?" -The best place to find more boxes is [Vagrant Cloud](https://vagrantcloud.com). -Vagrant Cloud has a public directory of freely available boxes that -run various platforms and technologies. Vagrant Cloud also has a great search +The best place to find more boxes is [HashiCorp's Atlas](https://atlas.hashicorp.com). +HashiCorp's Atlas has a public directory of freely available boxes that +run various platforms and technologies. HashiCorp's Atlas also has a great search feature to allow you to find the box you care about. -In addition to finding free boxes, Vagrant Cloud lets you host your own +In addition to finding free boxes, HashiCorp's Atlas lets you host your own boxes, as well as private boxes if you intend on creating boxes for your own organization. diff --git a/website/docs/source/v2/getting-started/share.html.md b/website/docs/source/v2/getting-started/share.html.md index 96c6ffe6a..694bd5d15 100644 --- a/website/docs/source/v2/getting-started/share.html.md +++ b/website/docs/source/v2/getting-started/share.html.md @@ -15,10 +15,10 @@ Vagrant Share lets you share your Vagrant environment to anyone around the world. It will give you a URL that will route directly to your Vagrant environment from any device in the world that is connected to the internet. -## Login to Vagrant Cloud +## Login to HashiCorp's Atlas Before being able to share your Vagrant environment, you'll need an account on -[Vagrant Cloud](https://vagrantcloud.com). Don't worry, it's free. +[HashiCorp's Atlas](https://atlas.hashicorp.com). Don't worry, it's free. Once you have an account, log in using `vagrant login`: diff --git a/website/docs/source/v2/hyperv/usage.html.md b/website/docs/source/v2/hyperv/usage.html.md index 7f3d9ccbe..e91c7e92e 100644 --- a/website/docs/source/v2/hyperv/usage.html.md +++ b/website/docs/source/v2/hyperv/usage.html.md @@ -17,5 +17,5 @@ admin rights. Vagrant will show you an error if it doesn't have the proper permissions. Boxes for Hyper-V can be easily found on -[Vagrant Cloud](https://vagrantcloud.com). To get started, you might +[HashiCorp's Atlas](https://atlas.hashicorp.com). To get started, you might want to try the `hashicorp/precise64` box. diff --git a/website/docs/source/v2/share/index.html.md b/website/docs/source/v2/share/index.html.md index 078fd5e59..20fde2781 100644 --- a/website/docs/source/v2/share/index.html.md +++ b/website/docs/source/v2/share/index.html.md @@ -34,4 +34,4 @@ to the left. We also have a section where we go into detail about the security implications of this feature. Vagrant Share requires an account with -[Vagrant Cloud](https://vagrantcloud.com) to be used. +[HashiCorp's Atlas](https://atlas.hashicorp.com) to be used. diff --git a/website/docs/source/v2/vagrantfile/machine_settings.html.md b/website/docs/source/v2/vagrantfile/machine_settings.html.md index a6f69c194..8ea060de5 100644 --- a/website/docs/source/v2/vagrantfile/machine_settings.html.md +++ b/website/docs/source/v2/vagrantfile/machine_settings.html.md @@ -20,7 +20,7 @@ for the machine to boot and be accessible. By default this is 300 seconds. `config.vm.box` - This configures what [box](/v2/boxes.html) the machine will be brought up against. The value here should be the name of an installed box or a shorthand name of a box in -[Vagrant Cloud](https://vagrantcloud.com). +[HashiCorp's Atlas](https://atlas.hashicorp.com).
@@ -28,7 +28,7 @@ of an installed box or a shorthand name of a box in the configured box on every `vagrant up`. If an update is found, Vagrant will tell the user. By default this is true. Updates will only be checked for boxes that properly support updates (boxes from -[Vagrant Cloud](https://vagrantcloud.com) +[HashiCorp's Atlas](https://atlas.hashicorp.com) or some other versioned box).
@@ -74,7 +74,7 @@ URL, then SSL certs will be verified.
`config.vm.box_url` - The URL that the configured box can be found at. -If `config.vm.box` is a shorthand to a box in [Vagrant Cloud](https://vagrantcloud.com) +If `config.vm.box` is a shorthand to a box in [HashiCorp's Atlas](https://atlas.hashicorp.com) then this value doesn't need to be specified. Otherwise, it should point to the proper place where the box can be found if it isn't installed. From b973186cb595680c54d55268cf6fcd0c04274ede Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Mon, 8 Dec 2014 17:42:00 -0800 Subject: [PATCH 78/83] Add vagrant-login to core ;) --- plugins/commands/login/client.rb | 81 +++++++++++++ plugins/commands/login/command.rb | 83 +++++++++++++ plugins/commands/login/errors.rb | 13 +++ plugins/commands/login/locales/en.yml | 26 +++++ .../login/middleware/add_authentication.rb | 35 ++++++ plugins/commands/login/plugin.rb | 35 ++++++ test/unit/base.rb | 1 + .../plugins/commands/login/client_test.rb | 109 ++++++++++++++++++ .../middleware/add_authentication_test.rb | 64 ++++++++++ vagrant.gemspec | 2 + 10 files changed, 449 insertions(+) create mode 100644 plugins/commands/login/client.rb create mode 100644 plugins/commands/login/command.rb create mode 100644 plugins/commands/login/errors.rb create mode 100644 plugins/commands/login/locales/en.yml create mode 100644 plugins/commands/login/middleware/add_authentication.rb create mode 100644 plugins/commands/login/plugin.rb create mode 100644 test/unit/plugins/commands/login/client_test.rb create mode 100644 test/unit/plugins/commands/login/middleware/add_authentication_test.rb diff --git a/plugins/commands/login/client.rb b/plugins/commands/login/client.rb new file mode 100644 index 000000000..01ba7649a --- /dev/null +++ b/plugins/commands/login/client.rb @@ -0,0 +1,81 @@ +require "rest_client" + +module VagrantPlugins + module LoginCommand + class Client + # Initializes a login client with the given Vagrant::Environment. + # + # @param [Vagrant::Environment] env + def initialize(env) + @env = env + end + + # Removes the token, effectively logging the user out. + def clear_token + token_path.delete if token_path.file? + end + + # Checks if the user is logged in by verifying their authentication + # token. + # + # @return [Boolean] + def logged_in? + token = self.token + return false if !token + + url = "#{Vagrant.server_url}/api/v1/authenticate" + + "?access_token=#{token}" + RestClient.get(url, content_type: :json) + true + rescue RestClient::Unauthorized + false + rescue SocketError + raise Errors::ServerUnreachable, url: Vagrant.server_url.to_s + end + + # Login logs a user in and returns the token for that user. The token + # is _not_ stored unless {#store_token} is called. + # + # @param [String] user + # @param [String] pass + # @return [String] token The access token, or nil if auth failed. + def login(user, pass) + url = "#{Vagrant.server_url}/api/v1/authenticate" + request = { "user" => { "login" => user, "password" => pass } } + response = RestClient.post( + url, JSON.dump(request), content_type: :json) + data = JSON.load(response.to_s) + data["token"] + rescue RestClient::Unauthorized + return nil + rescue SocketError + raise Errors::ServerUnreachable, url: Vagrant.server_url.to_s + end + + # Stores the given token locally, removing any previous tokens. + # + # @param [String] token + def store_token(token) + token_path.open("w") do |f| + f.write(token) + end + nil + end + + # Reads the access token if there is one, or returns nil otherwise. + # + # @return [String] + def token + token_path.read + rescue Errno::ENOENT + return nil + end + + protected + + def token_path + @env.data_dir.join("vagrant_login_token") + end + end + end +end diff --git a/plugins/commands/login/command.rb b/plugins/commands/login/command.rb new file mode 100644 index 000000000..e16b1afaf --- /dev/null +++ b/plugins/commands/login/command.rb @@ -0,0 +1,83 @@ +module VagrantPlugins + module LoginCommand + class Command < Vagrant.plugin("2", "command") + def self.synopsis + "log in to HashiCorp's Atlas" + end + + def execute + options = {} + + opts = OptionParser.new do |o| + o.banner = "Usage: vagrant login" + o.separator "" + o.on("-c", "--check", "Only checks if you're logged in") do |c| + options[:check] = c + end + + o.on("-k", "--logout", "Logs you out if you're logged in") do |k| + options[:logout] = k + end + end + + # Parse the options + argv = parse_options(opts) + return if !argv + + @client = Client.new(@env) + + # Determine what task we're actually taking based on flags + if options[:check] + return execute_check + elsif options[:logout] + return execute_logout + end + + # Let the user know what is going on. + @env.ui.output(I18n.t("login_command.command_header") + "\n") + + # If it is a private cloud installation, show that + if Vagrant.server_url != Vagrant::DEFAULT_SERVER_URL + @env.ui.output("Atlas URL: #{Vagrant.server_url}") + end + + # Ask for the username + login = nil + password = nil + while !login + login = @env.ui.ask("Atlas Username: ") + end + + while !password + password = @env.ui.ask("Password (will be hidden): ", echo: false) + end + + token = @client.login(login, password) + if !token + @env.ui.error(I18n.t("login_command.invalid_login")) + return 1 + end + + @client.store_token(token) + @env.ui.success(I18n.t("login_command.logged_in")) + 0 + end + + def execute_check + if @client.logged_in? + @env.ui.success(I18n.t("login_command.check_logged_in")) + return 0 + else + @env.ui.error(I18n.t("login_command.check_not_logged_in")) + return 1 + end + end + + def execute_logout + @client.clear_token + @env.ui.success(I18n.t("login_command.logged_out")) + return 0 + end + end + end +end diff --git a/plugins/commands/login/errors.rb b/plugins/commands/login/errors.rb new file mode 100644 index 000000000..a6888b597 --- /dev/null +++ b/plugins/commands/login/errors.rb @@ -0,0 +1,13 @@ +module VagrantPlugins + module LoginCommand + module Errors + class Error < Vagrant::Errors::VagrantError + error_namespace("login_command.errors") + end + + class ServerUnreachable < Error + error_key(:server_unreachable) + end + end + end +end diff --git a/plugins/commands/login/locales/en.yml b/plugins/commands/login/locales/en.yml new file mode 100644 index 000000000..b62b24196 --- /dev/null +++ b/plugins/commands/login/locales/en.yml @@ -0,0 +1,26 @@ +en: + login_command: + errors: + server_unreachable: |- + The Atlas server is not currently accepting connections. Please check + your network connection and try again later. + + check_logged_in: |- + You are already logged in. + check_not_logged_in: |- + You are not currently logged in. Please run `vagrant login` and provide + your login information to authenticate. + command_header: |- + In a moment we will ask for your username and password to HashiCorp's + Atlas. After authenticating, we will store an access token locally on + disk. Your login details will be transmitted over a secure connection, and + are never stored on disk locally. + + If you do not have an Atlas account, sign up at + https://atlas.hashicorp.com. + invalid_login: |- + Invalid username or password. Please try again. + logged_in: |- + You are now logged in. + logged_out: |- + You are logged out. diff --git a/plugins/commands/login/middleware/add_authentication.rb b/plugins/commands/login/middleware/add_authentication.rb new file mode 100644 index 000000000..bfb7dd46b --- /dev/null +++ b/plugins/commands/login/middleware/add_authentication.rb @@ -0,0 +1,35 @@ +require "uri" + +require_relative "../client" + +module VagrantPlugins + module LoginCommand + class AddAuthentication + def initialize(app, env) + @app = app + end + + def call(env) + client = Client.new(env[:env]) + token = client.token + + if token && Vagrant.server_url + server_uri = URI.parse(Vagrant.server_url) + + env[:box_urls].map! do |url| + u = URI.parse(url) + if u.host == server_uri.host + u.query ||= "" + u.query += "&" if u.query != "" + u.query += "access_token=#{token}" + end + + u.to_s + end + end + + @app.call(env) + end + end + end +end diff --git a/plugins/commands/login/plugin.rb b/plugins/commands/login/plugin.rb new file mode 100644 index 000000000..10ab352cf --- /dev/null +++ b/plugins/commands/login/plugin.rb @@ -0,0 +1,35 @@ +require "vagrant" + +module VagrantPlugins + module LoginCommand + autoload :Client, File.expand_path("../client", __FILE__) + autoload :Errors, File.expand_path("../errors", __FILE__) + + class Plugin < Vagrant.plugin("2") + name "vagrant-login" + description <<-DESC + Provides the login command and internal API access to Atlas. + DESC + + command(:login) do + require_relative "login" + init! + Push + end + + action_hook(:cloud_authenticated_boxes, :authenticate_box_url) do |hook| + require_relative "middleware/add_authentication" + hook.prepend(AddAuthentication) + end + + protected + + def self.init! + return if defined?(@_init) + I18n.load_path << File.expand_path("../locales/en.yml", __FILE__) + I18n.reload! + @_init = true + end + end + end +end diff --git a/test/unit/base.rb b/test/unit/base.rb index c7b11b958..e3c68c099 100644 --- a/test/unit/base.rb +++ b/test/unit/base.rb @@ -4,6 +4,7 @@ require "rubygems" # Gems require "checkpoint" require "rspec/autorun" +require "webmock/rspec" # Require Vagrant itself so we can reference the proper # classes to test. diff --git a/test/unit/plugins/commands/login/client_test.rb b/test/unit/plugins/commands/login/client_test.rb new file mode 100644 index 000000000..cad502ccf --- /dev/null +++ b/test/unit/plugins/commands/login/client_test.rb @@ -0,0 +1,109 @@ +require File.expand_path("../../../../base", __FILE__) + +require Vagrant.source_root.join("plugins/commands/login/command") + +describe VagrantPlugins::LoginCommand::Client do + include_context "unit" + + let(:env) { isolated_environment.create_vagrant_env } + + subject { described_class.new(env) } + + describe "#logged_in?" do + it "quickly returns false if no token is set" do + expect(subject).to_not be_logged_in + end + + it "returns true if the endpoint returns 200" do + subject.store_token("foo") + + response = { + "token" => "baz", + } + + headers = { "Content-Type" => "application/json" } + url = "#{Vagrant.server_url}/api/v1/authenticate?access_token=foo" + stub_request(:get, url). + with(headers: headers). + to_return(status: 200, body: JSON.dump(response)) + + expect(subject).to be_logged_in + end + + it "returns false if 401 is returned" do + subject.store_token("foo") + + url = "#{Vagrant.server_url}/api/v1/authenticate?access_token=foo" + stub_request(:get, url). + to_return(status: 401, body: "") + + expect(subject).to_not be_logged_in + end + + it "raises an exception if it can't reach the sever" do + subject.store_token("foo") + + url = "#{Vagrant.server_url}/api/v1/authenticate?access_token=foo" + stub_request(:get, url).to_raise(SocketError) + + expect { subject.logged_in? }. + to raise_error(VagrantPlugins::LoginCommand::Errors::ServerUnreachable) + end + end + + describe "#login" do + it "returns the access token after successful login" do + request = { + "user" => { + "login" => "foo", + "password" => "bar", + }, + } + + response = { + "token" => "baz", + } + + headers = { "Content-Type" => "application/json" } + + stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). + with(body: JSON.dump(request), headers: headers). + to_return(status: 200, body: JSON.dump(response)) + + expect(subject.login("foo", "bar")).to eq("baz") + end + + it "returns nil on bad login" do + stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). + to_return(status: 401, body: "") + + expect(subject.login("foo", "bar")).to be_nil + end + + it "raises an exception if it can't reach the sever" do + stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). + to_raise(SocketError) + + expect { subject.login("foo", "bar") }. + to raise_error(VagrantPlugins::LoginCommand::Errors::ServerUnreachable) + end + end + + describe "#token, #store_token, #clear_token" do + it "returns nil if there is no token" do + expect(subject.token).to be_nil + end + + it "stores the token and can re-access it" do + subject.store_token("foo") + expect(subject.token).to eq("foo") + expect(described_class.new(env).token).to eq("foo") + end + + it "deletes the token" do + subject.store_token("foo") + subject.clear_token + expect(subject.token).to be_nil + end + end +end diff --git a/test/unit/plugins/commands/login/middleware/add_authentication_test.rb b/test/unit/plugins/commands/login/middleware/add_authentication_test.rb new file mode 100644 index 000000000..826e5d46b --- /dev/null +++ b/test/unit/plugins/commands/login/middleware/add_authentication_test.rb @@ -0,0 +1,64 @@ +require File.expand_path("../../../../../base", __FILE__) + +require Vagrant.source_root.join("plugins/commands/login/middleware/add_authentication") + +describe VagrantPlugins::LoginCommand::AddAuthentication do + include_context "unit" + + let(:app) { lambda { |env| } } + let(:env) { { + env: iso_env, + } } + + let(:iso_env) { isolated_environment.create_vagrant_env } + let(:server_url) { "http://foo.com" } + + subject { described_class.new(app, env) } + + before do + allow(Vagrant).to receive(:server_url).and_return(server_url) + end + + describe "#call" do + it "does nothing if we have no server set" do + allow(Vagrant).to receive(:server_url).and_return(nil) + VagrantPlugins::LoginCommand::Client.new(iso_env).store_token("foo") + + original = ["foo", "#{server_url}/bar"] + env[:box_urls] = original.dup + + subject.call(env) + + expect(env[:box_urls]).to eq(original) + end + + it "does nothing if we aren't logged in" do + original = ["foo", "#{server_url}/bar"] + env[:box_urls] = original.dup + + subject.call(env) + + expect(env[:box_urls]).to eq(original) + end + + it "appends the access token to the URL of server URLs" do + token = "foobarbaz" + VagrantPlugins::LoginCommand::Client.new(iso_env).store_token(token) + + original = [ + "http://google.com/box.box", + "#{server_url}/foo.box", + "#{server_url}/bar.box?arg=true", + ] + + expected = original.dup + expected[1] = "#{original[1]}?access_token=#{token}" + expected[2] = "#{original[2]}&access_token=#{token}" + + env[:box_urls] = original.dup + subject.call(env) + + expect(env[:box_urls]).to eq(expected) + end + end +end diff --git a/vagrant.gemspec b/vagrant.gemspec index 23931940b..a2115b055 100644 --- a/vagrant.gemspec +++ b/vagrant.gemspec @@ -26,6 +26,7 @@ Gem::Specification.new do |s| s.add_dependency "net-sftp", "~> 2.1" s.add_dependency "net-scp", "~> 1.1.0" s.add_dependency "rb-kqueue", "~> 0.2.0" + s.add_dependency "rest-client", "~> 1.7" s.add_dependency "wdm", "~> 0.1.0" s.add_dependency "winrm", "~> 1.1.3" @@ -34,6 +35,7 @@ Gem::Specification.new do |s| s.add_development_dependency "rake" s.add_development_dependency "rspec", "~> 2.14.0" + s.add_development_dependency "webmock", "~> 1.20" s.add_development_dependency "fake_ftp", "~> 0.1" # The following block of code determines the files that should be included From 3d8a1ec3fc21541e6840eafe0bc1f245c3250a96 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Mon, 8 Dec 2014 17:48:22 -0800 Subject: [PATCH 79/83] Fix some rename shit --- plugins/commands/login/plugin.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/commands/login/plugin.rb b/plugins/commands/login/plugin.rb index 10ab352cf..efb84a556 100644 --- a/plugins/commands/login/plugin.rb +++ b/plugins/commands/login/plugin.rb @@ -12,9 +12,9 @@ module VagrantPlugins DESC command(:login) do - require_relative "login" + require_relative "command" init! - Push + Command end action_hook(:cloud_authenticated_boxes, :authenticate_box_url) do |hook| From 62065e013a928772b9ccb8e594056f096bbd182d Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Mon, 8 Dec 2014 18:24:01 -0800 Subject: [PATCH 80/83] Read and return login errors from Atlas --- plugins/commands/login/client.rb | 50 +++++++++++++++++---------- plugins/commands/login/errors.rb | 4 +++ plugins/commands/login/locales/en.yml | 4 +++ 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/plugins/commands/login/client.rb b/plugins/commands/login/client.rb index 01ba7649a..f06772be9 100644 --- a/plugins/commands/login/client.rb +++ b/plugins/commands/login/client.rb @@ -23,14 +23,12 @@ module VagrantPlugins token = self.token return false if !token - url = "#{Vagrant.server_url}/api/v1/authenticate" + - "?access_token=#{token}" - RestClient.get(url, content_type: :json) - true - rescue RestClient::Unauthorized - false - rescue SocketError - raise Errors::ServerUnreachable, url: Vagrant.server_url.to_s + with_error_handling do + url = "#{Vagrant.server_url}/api/v1/authenticate" + + "?access_token=#{token}" + RestClient.get(url, content_type: :json) + true + end end # Login logs a user in and returns the token for that user. The token @@ -40,16 +38,14 @@ module VagrantPlugins # @param [String] pass # @return [String] token The access token, or nil if auth failed. def login(user, pass) - url = "#{Vagrant.server_url}/api/v1/authenticate" - request = { "user" => { "login" => user, "password" => pass } } - response = RestClient.post( - url, JSON.dump(request), content_type: :json) - data = JSON.load(response.to_s) - data["token"] - rescue RestClient::Unauthorized - return nil - rescue SocketError - raise Errors::ServerUnreachable, url: Vagrant.server_url.to_s + with_error_handling do + url = "#{Vagrant.server_url}/api/v1/authenticate" + request = { "user" => { "login" => user, "password" => pass } } + response = RestClient.post( + url, JSON.dump(request), content_type: :json) + data = JSON.load(response.to_s) + data["token"] + end end # Stores the given token locally, removing any previous tokens. @@ -73,6 +69,24 @@ module VagrantPlugins protected + def with_error_handling(&block) + yield + rescue RestClient::Unauthorized + false + rescue RestClient::NotAcceptable => e + begin + errors = JSON.parse(e.response)["errors"] + .map { |h| h["message"] } + .join("\n") + + raise Errors::ServerError, errors: errors + rescue JSON::ParserError; end + + raise "An unexpected error occurred: #{e.inspect}" + rescue SocketError + raise Errors::ServerUnreachable, url: Vagrant.server_url.to_s + end + def token_path @env.data_dir.join("vagrant_login_token") end diff --git a/plugins/commands/login/errors.rb b/plugins/commands/login/errors.rb index a6888b597..614c37cf6 100644 --- a/plugins/commands/login/errors.rb +++ b/plugins/commands/login/errors.rb @@ -5,6 +5,10 @@ module VagrantPlugins error_namespace("login_command.errors") end + class ServerError < Error + error_key(:server_error) + end + class ServerUnreachable < Error error_key(:server_unreachable) end diff --git a/plugins/commands/login/locales/en.yml b/plugins/commands/login/locales/en.yml index b62b24196..51020df1d 100644 --- a/plugins/commands/login/locales/en.yml +++ b/plugins/commands/login/locales/en.yml @@ -1,6 +1,10 @@ en: login_command: errors: + server_error: |- + The Atlas server responded with an not-OK response: + + %{errors} server_unreachable: |- The Atlas server is not currently accepting connections. Please check your network connection and try again later. From bb7b954a5feaa613d2ce91f4e19080a71b176666 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Mon, 8 Dec 2014 22:06:41 -0800 Subject: [PATCH 81/83] Use the new namespace in Atlas config --- plugins/pushes/atlas/config.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/plugins/pushes/atlas/config.rb b/plugins/pushes/atlas/config.rb index 5c4a1a7cf..d4d2da2c5 100644 --- a/plugins/pushes/atlas/config.rb +++ b/plugins/pushes/atlas/config.rb @@ -139,12 +139,8 @@ module VagrantPlugins # @return [String, nil] # the token, or nil if it does not exist def token_from_vagrant_login(env) - if defined?(VagrantPlugins::Login::Client) - client = VagrantPlugins::Login::Client.new(env) - return client.token - end - - nil + client = VagrantPlugins::LoginCommand::Client.new(env) + client.token end end end From 9ec16774ace424cdf8c2d7d60a7c90c2ea9b3e7d Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 9 Dec 2014 00:08:23 -0800 Subject: [PATCH 82/83] Fix failing specs --- test/unit/plugins/commands/login/client_test.rb | 2 +- test/unit/plugins/pushes/atlas/config_test.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit/plugins/commands/login/client_test.rb b/test/unit/plugins/commands/login/client_test.rb index cad502ccf..7c9480442 100644 --- a/test/unit/plugins/commands/login/client_test.rb +++ b/test/unit/plugins/commands/login/client_test.rb @@ -77,7 +77,7 @@ describe VagrantPlugins::LoginCommand::Client do stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). to_return(status: 401, body: "") - expect(subject.login("foo", "bar")).to be_nil + expect(subject.login("foo", "bar")).to be(false) end it "raises an exception if it can't reach the sever" do diff --git a/test/unit/plugins/pushes/atlas/config_test.rb b/test/unit/plugins/pushes/atlas/config_test.rb index e62a5b86e..1600e967a 100644 --- a/test/unit/plugins/pushes/atlas/config_test.rb +++ b/test/unit/plugins/pushes/atlas/config_test.rb @@ -59,6 +59,7 @@ describe VagrantPlugins::AtlasPush::Config do allow(machine).to receive(:env) .and_return(double("env", root_path: "", + data_dir: Pathname.new(""), )) subject.app = "sethvargo/bacon" From d8b73fc31974fd190234a890a4c5bf82cccd57d3 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 9 Dec 2014 10:14:32 -0800 Subject: [PATCH 83/83] Add changelog entry for vagrant-login --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53565b085..e0d9adb5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ FEATURES: providers are chosen before later ones. [GH-3812] - If the default insecure keypair is used, Vagrant will automatically replace it with a randomly generated keypair on first `vagrant up`. [GH-2608] + - Vagrant Login is now part of Vagrant core - Chef Zero provisioner: Use Chef 11's "local" mode to run recipes against an in-memory Chef Server - Chef Apply provisioner: Specify inline Chef recipes and recipe snippets