Merge remote-tracking branch 'upcoming/master'

This commit is contained in:
Mitchell Hashimoto 2014-12-09 16:18:18 -08:00
commit 02a615a646
84 changed files with 4623 additions and 43 deletions

View File

@ -15,6 +15,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

View File

@ -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

View File

@ -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]

View File

@ -539,6 +539,41 @@ module Vagrant
end
end
# This executes the push with the given name, raising any exceptions that
# occur.
#
# Precondition: the push is not nil and exists.
def push(name)
@logger.info("Getting push: #{name}")
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
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<Symbol>]
def pushes
self.vagrantfile.config.push.__compiled_pushes.keys
end
# This returns a machine with the proper provider for this environment.
# The machine named by `name` must be in this environment.
#

View File

@ -556,6 +556,22 @@ 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 PushStrategyNotLoaded < VagrantError
error_key(:push_strategy_not_loaded)
end
class PushStrategyNotProvided < VagrantError
error_key(:push_strategy_not_provided)
end
class RSyncError < VagrantError
error_key(:rsync_error)
end

View File

@ -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

View File

@ -54,6 +54,11 @@ module Vagrant
# @return [Hash<Symbol, Registry>]
attr_reader :provider_capabilities
# This contains all the push implementations by name.
#
# @return [Registry<Symbol, Array<Class, Hash>>]
attr_reader :pushes
# This contains all the synced folder implementations by name.
#
# @return [Registry<Symbol, Array<Class, Integer>>]
@ -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

View File

@ -172,6 +172,28 @@ 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 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]

View File

@ -221,6 +221,18 @@ module Vagrant
data[:provisioners]
end
# 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, options=nil, &block)
components.pushes.register(name.to_sym) do
[block.call, options]
end
nil
end
# Registers additional synced folder implementations.
#
# @param [String] name Name of the implementation.

View File

@ -0,0 +1,27 @@
module Vagrant
module Plugin
module V2
class Push
attr_reader :env
attr_reader :config
# Initializes the pusher with the given environment the push
# configuration.
#
# @param [Environment] env
# @param [Object] config Push configuration
def initialize(env, config)
@env = env
@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

View File

@ -49,6 +49,21 @@ module Vagrant
end
end
# Return the number of elements in this registry.
#
# @return [Fixnum]
def length
@items.keys.length
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.

View File

@ -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

View File

@ -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 ""

View File

@ -0,0 +1,95 @@
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
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
# 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)
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.
#
# @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 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
end
end
end

View File

@ -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

View File

@ -0,0 +1,17 @@
module VagrantPlugins
module LoginCommand
module Errors
class Error < Vagrant::Errors::VagrantError
error_namespace("login_command.errors")
end
class ServerError < Error
error_key(:server_error)
end
class ServerUnreachable < Error
error_key(:server_unreachable)
end
end
end
end

View File

@ -0,0 +1,30 @@
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.
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.

View File

@ -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

View File

@ -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 "command"
init!
Command
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

View File

@ -0,0 +1,68 @@
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 = validate_pushes!(@env.pushes, argv[0])
@logger.debug("'push' environment with strategy: `#{name}'")
@env.push(name)
0
end
# Validate that the given list of names corresponds to valid pushes.
#
# @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
# @raise Vagrant::Errors::PushStrategyNotDefined
# if the given push name do not correspond to a push strategy
#
# @param [Array<Symbol>] pushes
# the list of pushes defined by the environment
# @param [String] name
# the name provided by the user on the command line
#
# @return [Symbol]
# the compiled list of pushes
#
def validate_pushes!(pushes, name = nil)
if pushes.nil? || pushes.empty?
raise Vagrant::Errors::PushesNotDefined
end
if name.nil?
if pushes.length == 1
return pushes.first.to_sym
else
raise Vagrant::Errors::PushStrategyNotProvided, pushes: pushes
end
end
if !pushes.include?(name)
raise Vagrant::Errors::PushStrategyNotDefined,
name: name,
pushes: pushes
end
return name.to_sym
end
end
end
end

View File

@ -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

View File

@ -0,0 +1,127 @@
require "vagrant"
module VagrantPlugins
module Kernel_V2
class PushConfig < Vagrant.plugin("2", :config)
VALID_OPTIONS = [:strategy].freeze
attr_accessor :name
def initialize
@logger = Log4r::Logger.new("vagrant::config::push")
# Internal state
@__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 = name
strategy = tuples[0][0] if tuples[0]
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: "<push config: #{name}>",
message: e.message
end
config.finalize!
# Store it for retrieval later
@__compiled_pushes[name] = [strategy, config]
end
@__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)
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 compiled pushes as a hash by name.
#
# @return [Hash<Symbol, Array<Class, Object>>]
def __compiled_pushes
raise "Must finalize first!" if !@__finalized
@__compiled_pushes.dup
end
end
end
end

View File

@ -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

View File

@ -0,0 +1,147 @@
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.
#
# @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<String>]
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
# deployed.
#
# @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
@address = UNSET_VALUE
@token = UNSET_VALUE
@app = UNSET_VALUE
@dir = UNSET_VALUE
@vcs = UNSET_VALUE
@includes = []
@excludes = []
@uploader_path = 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!
@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
@vcs = true if @vcs == UNSET_VALUE
end
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",
)
end
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
# 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)
client = VagrantPlugins::LoginCommand::Client.new(env)
client.token
end
end
end
end

View File

@ -0,0 +1,13 @@
module VagrantPlugins
module AtlasPush
module Errors
class Error < Vagrant::Errors::VagrantError
error_namespace("atlas_push.errors")
end
class UploaderNotFound < Error
error_key(:uploader_not_found)
end
end
end
end

View File

@ -0,0 +1,22 @@
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
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
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.

View File

@ -0,0 +1,35 @@
require "vagrant"
module VagrantPlugins
module AtlasPush
autoload :Errors, File.expand_path("../errors", __FILE__)
class Plugin < Vagrant.plugin("2")
name "atlas"
description <<-DESC
Deploy using HashiCorp's Atlas service.
DESC
config(:atlas, :push) do
require_relative "config"
init!
Config
end
push(:atlas) do
require_relative "push"
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

View File

@ -0,0 +1,57 @@
require "vagrant/util/safe_exec"
require "vagrant/util/subprocess"
require "vagrant/util/which"
module VagrantPlugins
module AtlasPush
class Push < Vagrant.plugin("2", :push)
UPLOADER_BIN = "atlas-upload".freeze
def push
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.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)
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?
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
end

View File

@ -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

View File

@ -0,0 +1,130 @@
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<String>]
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
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
end

View File

@ -0,0 +1,11 @@
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:
config.push.define "ftp" do |push|
push.%{attribute} = "..."
end

View File

@ -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

116
plugins/pushes/ftp/push.rb Normal file
View File

@ -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<String>]
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<String>]
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<String>] list
# the filepaths
# @param [Array<String>] 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<String>]
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

View File

@ -0,0 +1,74 @@
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
def initialize
@app = UNSET_VALUE
@dir = UNSET_VALUE
@git_bin = UNSET_VALUE
@remote = 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
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
{ "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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -0,0 +1,136 @@
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)
# 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, app, dir)
end
# Push to Heroku
git_push_heroku(config.remote, 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
# 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]
def git_dir(path)
"#{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
def git_push_heroku(remote, branch, path)
execute!("git",
"--git-dir", git_dir(path),
"--work-tree", path,
"push", remote, "#{branch}:master",
)
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

View File

@ -0,0 +1,48 @@
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 :inline
def initialize
@script = UNSET_VALUE
@inline = UNSET_VALUE
end
def finalize!
@script = nil if @script == UNSET_VALUE
@inline = nil if @inline == UNSET_VALUE
end
def validate(machine)
errors = _detected_errors
if missing?(@script) && missing?(@inline)
errors << I18n.t("local_exec_push.errors.missing_attribute",
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
private
# Determine if the given string is "missing" (blank)
# @return [true, false]
def missing?(obj)
obj.to_s.strip.empty?
end
end
end
end

View File

@ -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

View File

@ -0,0 +1,22 @@
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:
%{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

View File

@ -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

View File

@ -0,0 +1,54 @@
require "fileutils"
require "tempfile"
require "vagrant/util/subprocess"
require_relative "errors"
module VagrantPlugins
module LocalExecPush
class Push < Vagrant.plugin("2", :push)
def push
if config.inline
execute_inline!(config.inline)
else
execute_script!(config.script)
end
end
# 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)
if result.exit_code != 0
raise Errors::CommandFailed,
cmd: cmd.join(" "),
stdout: result.stdout,
stderr: result.stderr
end
result
end
end
end
end

View File

@ -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

View File

@ -0,0 +1,22 @@
require "vagrant"
module VagrantPlugins
module NoopDeploy
class Plugin < Vagrant.plugin("2")
name "noop"
description <<-DESC
Literally do nothing
DESC
config(:noop, :push) do
require File.expand_path("../config", __FILE__)
Config
end
push(:noop) do
require File.expand_path("../push", __FILE__)
Push
end
end
end
end

View File

@ -0,0 +1,9 @@
module VagrantPlugins
module NoopDeploy
class Push < Vagrant.plugin("2", :push)
def push
puts "pushed"
end
end
end
end

View File

@ -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
@ -946,6 +944,29 @@ 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.define "ftp" do |push|
# ... push-specific options
end
push_strategy_not_defined: |-
The push strategy '%{name}' is not defined in the Vagrantfile. Defined
strategy names are:
%{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}
push_strategy_not_provided: |-
The Vagrantfile defines more than one 'push' strategy. Please specify a
strategy. Defined strategy names 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
@ -1892,3 +1913,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."

View File

@ -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.

View File

@ -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(false)
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

View File

@ -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

View File

@ -0,0 +1,118 @@
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"
let(:env) do
isolated_environment.tap do |env|
env.vagrantfile("")
env.create_vagrant_env
end
end
let(:argv) { [] }
let(:pushes) { {} }
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!)
.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
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) { [] }
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) { [: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 "returns that push" do
expect(subject.validate_pushes!(pushes, :noop)).to eq(:noop)
end
end
end
context "when no strategy is given" do
it "returns the strategy" do
expect(subject.validate_pushes!(pushes)).to eq(:noop)
end
end
end
context "when there are multiple pushes defined" do
let(:noop) { double("noop") }
let(:ftp) { double("ftp") }
let(:pushes) { [:noop, :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 "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 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

View File

@ -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

View File

@ -0,0 +1,223 @@
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 "#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!
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: "",
data_dir: Pathname.new(""),
))
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 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 = ""
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

View File

@ -0,0 +1,153 @@
require_relative "../../../base"
require Vagrant.source_root.join("plugins/pushes/atlas/config")
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__)
)
end
let(:config) do
VagrantPlugins::AtlasPush::Config.new.tap do |c|
c.finalize!
end
end
subject { described_class.new(env, 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::AtlasPush::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, 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, env.root_path.to_s)
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, env.root_path.to_s)
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, env.root_path.to_s)
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
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
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 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)
expect(subject.uploader_path).to be_nil
end
end
end

View File

@ -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

View File

@ -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 ." 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

View File

@ -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

View File

@ -0,0 +1,99 @@
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 "#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"
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 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

View File

@ -0,0 +1,324 @@
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",
)
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?)
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, 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:master",
)
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 "#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"))
.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 "#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" }
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

View File

@ -0,0 +1,85 @@
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 "#script" do
it "defaults to nil" do
subject.finalize!
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
describe "#validate" do
before do
allow(machine).to receive(:env)
.and_return(double("env",
root_path: "",
))
subject.finalize!
end
let(:result) { subject.validate(machine) }
let(:errors) { result["Local Exec push"] }
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
end

View File

@ -0,0 +1,139 @@
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",
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
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(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
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

View File

@ -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

View File

@ -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

View File

@ -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" }

View File

@ -322,6 +322,42 @@ 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

View File

@ -90,6 +90,39 @@ 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 "#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

View File

@ -23,8 +23,10 @@ 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 "rest-client", "~> 1.7"
s.add_dependency "wdm", "~> 0.1.0"
s.add_dependency "winrm", "~> 1.1.3"
@ -33,6 +35,8 @@ 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
# in the gem. It does this by reading all the files in the directory where

View File

@ -288,6 +288,17 @@
</ul>
<% end %>
<li<%= sidebar_current("push") %>><a href="/v2/push/index.html">Push</a></li>
<% if sidebar_section == "push" %>
<ul class="sub unstyled">
<li<%= sidebar_current("push-atlas") %>><a href="/v2/push/atlas.html">Atlas</a></li>
<li<%= sidebar_current("push-ftp") %>><a href="/v2/push/ftp.html">FTP / SFTP</a></li>
<li<%= sidebar_current("push-heroku") %>><a href="/v2/push/heroku.html">Heroku</a></li>
<li<%= sidebar_current("push-local-exec") %>><a href="/v2/push/local-exec.html">Local Exec</a></li>
</ul>
<% end %>
<li<%= sidebar_current("other") %>><a href="/v2/other/index.html">Other</a></li>
<% if sidebar_section == "other" %>

View File

@ -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.

View File

@ -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.

View File

@ -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.
<div class="alert alert-block alert-info">
<strong>You don't need to manually make the metadata.</strong> If you
have an account with <a href="https://vagrantcloud.com">Vagrant Cloud</a>, you
can create boxes there, and Vagrant Cloud automatically creates
have an account with <a href="https://atlas.hashicorp.com">HashiCorp's Atlas</a>, you
can create boxes there, and HashiCorp's Atlas automatically creates
the metadata for you. The format is still documented here.
</div>

View File

@ -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

View File

@ -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.
<div class="alert alert-block alert-warn">
<strong>Checksums for versioned boxes or boxes from Vagrant Cloud:</strong>
For boxes from Vagrant Cloud, the checksums are embedded in the metadata
<strong>Checksums for versioned boxes or boxes from HashiCorp's Atlas:</strong>
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.
</div>

View File

@ -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).

View File

@ -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.

View File

@ -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`:

View File

@ -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.

View File

@ -0,0 +1,67 @@
---
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.
- `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
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"

View File

@ -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
```

View File

@ -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.
<div class="alert alert-warn">
<p>
<strong>Warning:</strong> 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 <a href="https://devcenter.heroku.com">Heroku documentation</a>.
</p>
</div>
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"

View File

@ -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
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
`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"

View File

@ -0,0 +1,60 @@
---
page_title: "Vagrant Push - Local Exec Strategy"
sidebar_current: "push-local-exec"
description: |-
The Vagrant Push Local Exec strategy pushes your application's code using a
user-defined script.
---
# 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.
<div class="alert alert-warn">
<p>
<strong>Warning:</strong> The Vagrant Push Local Exec strategy does not
perform any validation on the correctness of the shell script.
</p>
</div>
The Vagrant Push Local Exec strategy supports the following configuration
options:
- `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
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.inline = <<-SCRIPT
scp . /var/www/website
SCRIPT
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.script = "my-script.sh"
end
```
And then invoke the push with Vagrant:
```shell
$ vagrant push
```

View File

@ -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.

View File

@ -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).
<hr>
@ -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).
<hr>
@ -74,7 +74,7 @@ URL, then SSL certs will be verified.
<hr>
`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.