Merge remote-tracking branch 'upcoming/master'
This commit is contained in:
commit
02a615a646
|
@ -15,6 +15,7 @@ FEATURES:
|
||||||
providers are chosen before later ones. [GH-3812]
|
providers are chosen before later ones. [GH-3812]
|
||||||
- If the default insecure keypair is used, Vagrant will automatically replace
|
- If the default insecure keypair is used, Vagrant will automatically replace
|
||||||
it with a randomly generated keypair on first `vagrant up`. [GH-2608]
|
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
|
- Chef Zero provisioner: Use Chef 11's "local" mode to run recipes against an
|
||||||
in-memory Chef Server
|
in-memory Chef Server
|
||||||
- Chef Apply provisioner: Specify inline Chef recipes and recipe snippets
|
- Chef Apply provisioner: Specify inline Chef recipes and recipe snippets
|
||||||
|
|
|
@ -123,6 +123,7 @@ module Vagrant
|
||||||
c.register([:"2", :host]) { Plugin::V2::Host }
|
c.register([:"2", :host]) { Plugin::V2::Host }
|
||||||
c.register([:"2", :provider]) { Plugin::V2::Provider }
|
c.register([:"2", :provider]) { Plugin::V2::Provider }
|
||||||
c.register([:"2", :provisioner]) { Plugin::V2::Provisioner }
|
c.register([:"2", :provisioner]) { Plugin::V2::Provisioner }
|
||||||
|
c.register([:"2", :push]) { Plugin::V2::Push }
|
||||||
c.register([:"2", :synced_folder]) { Plugin::V2::SyncedFolder }
|
c.register([:"2", :synced_folder]) { Plugin::V2::SyncedFolder }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -147,7 +147,7 @@ module Vagrant
|
||||||
# element is an authenticated URL.
|
# element is an authenticated URL.
|
||||||
# @param [Hash] env
|
# @param [Hash] env
|
||||||
# @param [Bool] expanded True if the metadata URL was expanded with
|
# @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)
|
def add_from_metadata(url, env, expanded)
|
||||||
original_url = env[:box_url]
|
original_url = env[:box_url]
|
||||||
provider = env[:box_provider]
|
provider = env[:box_provider]
|
||||||
|
|
|
@ -539,6 +539,41 @@ module Vagrant
|
||||||
end
|
end
|
||||||
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.
|
# This returns a machine with the proper provider for this environment.
|
||||||
# The machine named by `name` must be in this environment.
|
# The machine named by `name` must be in this environment.
|
||||||
#
|
#
|
||||||
|
|
|
@ -556,6 +556,22 @@ module Vagrant
|
||||||
error_key(:plugin_uninstall_system)
|
error_key(:plugin_uninstall_system)
|
||||||
end
|
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
|
class RSyncError < VagrantError
|
||||||
error_key(:rsync_error)
|
error_key(:rsync_error)
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,6 +16,7 @@ module Vagrant
|
||||||
autoload :Manager, "vagrant/plugin/v2/manager"
|
autoload :Manager, "vagrant/plugin/v2/manager"
|
||||||
autoload :Plugin, "vagrant/plugin/v2/plugin"
|
autoload :Plugin, "vagrant/plugin/v2/plugin"
|
||||||
autoload :Provider, "vagrant/plugin/v2/provider"
|
autoload :Provider, "vagrant/plugin/v2/provider"
|
||||||
|
autoload :Push, "vagrant/plugin/v2/push"
|
||||||
autoload :Provisioner, "vagrant/plugin/v2/provisioner"
|
autoload :Provisioner, "vagrant/plugin/v2/provisioner"
|
||||||
autoload :SyncedFolder, "vagrant/plugin/v2/synced_folder"
|
autoload :SyncedFolder, "vagrant/plugin/v2/synced_folder"
|
||||||
end
|
end
|
||||||
|
|
|
@ -54,6 +54,11 @@ module Vagrant
|
||||||
# @return [Hash<Symbol, Registry>]
|
# @return [Hash<Symbol, Registry>]
|
||||||
attr_reader :provider_capabilities
|
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.
|
# This contains all the synced folder implementations by name.
|
||||||
#
|
#
|
||||||
# @return [Registry<Symbol, Array<Class, Integer>>]
|
# @return [Registry<Symbol, Array<Class, Integer>>]
|
||||||
|
@ -71,6 +76,7 @@ module Vagrant
|
||||||
@host_capabilities = Hash.new { |h, k| h[k] = Registry.new }
|
@host_capabilities = Hash.new { |h, k| h[k] = Registry.new }
|
||||||
@providers = Registry.new
|
@providers = Registry.new
|
||||||
@provider_capabilities = Hash.new { |h, k| h[k] = Registry.new }
|
@provider_capabilities = Hash.new { |h, k| h[k] = Registry.new }
|
||||||
|
@pushes = Registry.new
|
||||||
@synced_folders = Registry.new
|
@synced_folders = Registry.new
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -172,6 +172,28 @@ module Vagrant
|
||||||
end
|
end
|
||||||
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.
|
# This returns all synced folder implementations.
|
||||||
#
|
#
|
||||||
# @return [Registry]
|
# @return [Registry]
|
||||||
|
|
|
@ -221,6 +221,18 @@ module Vagrant
|
||||||
data[:provisioners]
|
data[:provisioners]
|
||||||
end
|
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.
|
# Registers additional synced folder implementations.
|
||||||
#
|
#
|
||||||
# @param [String] name Name of the implementation.
|
# @param [String] name Name of the implementation.
|
||||||
|
|
|
@ -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
|
|
@ -34,7 +34,7 @@ module Vagrant
|
||||||
def has_key?(key)
|
def has_key?(key)
|
||||||
@items.has_key?(key)
|
@items.has_key?(key)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns an array populated with the keys of this object.
|
# Returns an array populated with the keys of this object.
|
||||||
#
|
#
|
||||||
# @return [Array]
|
# @return [Array]
|
||||||
|
@ -49,6 +49,21 @@ module Vagrant
|
||||||
end
|
end
|
||||||
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
|
# Merge one registry with another and return a completely new
|
||||||
# registry. Note that the result cache is completely busted, so
|
# registry. Note that the result cache is completely busted, so
|
||||||
# any gets on the new registry will result in a cache miss.
|
# any gets on the new registry will result in a cache miss.
|
||||||
|
|
|
@ -5,12 +5,12 @@ require "thread"
|
||||||
module Vagrant
|
module Vagrant
|
||||||
@@global_lock = Mutex.new
|
@@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
|
# use. API calls will be made to this for various functions
|
||||||
# of Vagrant that may require remote access.
|
# of Vagrant that may require remote access.
|
||||||
#
|
#
|
||||||
# @return [String]
|
# @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
|
# This holds a global lock for the duration of the block. This should
|
||||||
# be invoked around anything that is modifying process state (such as
|
# be invoked around anything that is modifying process state (such as
|
||||||
|
|
|
@ -47,7 +47,7 @@ module VagrantPlugins
|
||||||
end
|
end
|
||||||
|
|
||||||
o.separator ""
|
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 "or a URL, or a local .box file, or a local .json file containing"
|
||||||
o.separator "the catalog metadata."
|
o.separator "the catalog metadata."
|
||||||
o.separator ""
|
o.separator ""
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -25,6 +25,11 @@ module VagrantPlugins
|
||||||
PackageConfig
|
PackageConfig
|
||||||
end
|
end
|
||||||
|
|
||||||
|
config("push") do
|
||||||
|
require File.expand_path("../config/push", __FILE__)
|
||||||
|
PushConfig
|
||||||
|
end
|
||||||
|
|
||||||
config("vagrant") do
|
config("vagrant") do
|
||||||
require File.expand_path("../config/vagrant", __FILE__)
|
require File.expand_path("../config/vagrant", __FILE__)
|
||||||
VagrantConfig
|
VagrantConfig
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,9 @@
|
||||||
|
module VagrantPlugins
|
||||||
|
module NoopDeploy
|
||||||
|
class Push < Vagrant.plugin("2", :push)
|
||||||
|
def push
|
||||||
|
puts "pushed"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -369,7 +369,7 @@ en:
|
||||||
provider. Double-check your requested provider to verify you didn't
|
provider. Double-check your requested provider to verify you didn't
|
||||||
simply misspell it.
|
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.
|
released.
|
||||||
|
|
||||||
Name: %{name}
|
Name: %{name}
|
||||||
|
@ -388,7 +388,7 @@ en:
|
||||||
box_add_short_not_found: |-
|
box_add_short_not_found: |-
|
||||||
The box '%{name}' could not be found or
|
The box '%{name}' could not be found or
|
||||||
could not be accessed in the remote catalog. If this is a private
|
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
|
`vagrant login`. Also, please double-check the name. The expanded
|
||||||
URL and error message are shown below:
|
URL and error message are shown below:
|
||||||
|
|
||||||
|
@ -550,16 +550,14 @@ en:
|
||||||
|
|
||||||
%{versions}
|
%{versions}
|
||||||
box_server_not_set: |-
|
box_server_not_set: |-
|
||||||
A URL to a Vagrant Cloud server is not set, so boxes cannot
|
A URL to an Atlas server is not set, so boxes cannot be added with a
|
||||||
be added with a shorthand ("mitchellh/precise64") format.
|
shorthand ("mitchellh/precise64") format. You may also be seeing this
|
||||||
You may also be seeing this error if you meant to type in
|
error if you meant to type in a path to a box file which doesn't exist
|
||||||
a path to a box file which doesn't exist locally on your
|
locally on your system.
|
||||||
system.
|
|
||||||
|
|
||||||
To set a URL to a Vagrant Cloud server, set the
|
To set a URL to an Atlas server, set the `VAGRANT_SERVER_URL`
|
||||||
`VAGRANT_SERVER_URL` environmental variable. Or, if you
|
environmental variable. Or, if you meant to use a file path, make sure
|
||||||
meant to use a file path, make sure the path to the file
|
the path to the file is valid.
|
||||||
is valid.
|
|
||||||
box_update_multi_provider: |-
|
box_update_multi_provider: |-
|
||||||
You requested to update the box '%{name}'. This box has
|
You requested to update the box '%{name}'. This box has
|
||||||
multiple providers. You must explicitly select a single
|
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
|
You can however, install a plugin with the same name to replace
|
||||||
these plugins. User-installed plugins take priority over
|
these plugins. User-installed plugins take priority over
|
||||||
system-installed plugins.
|
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: |-
|
package_include_symlink: |-
|
||||||
A file or directory you're attempting to include with your packaged
|
A file or directory you're attempting to include with your packaged
|
||||||
box has symlinks in it. Vagrant cannot include symlinks in the
|
box has symlinks in it. Vagrant cannot include symlinks in the
|
||||||
|
@ -1892,3 +1913,7 @@ en:
|
||||||
You must include both public and private keys.
|
You must include both public and private keys.
|
||||||
must_accept_keys: |-
|
must_accept_keys: |-
|
||||||
You must accept keys when running highstate with master!
|
You must accept keys when running highstate with master!
|
||||||
|
|
||||||
|
pushes:
|
||||||
|
file:
|
||||||
|
no_destination: "File destination must be specified."
|
||||||
|
|
|
@ -4,6 +4,7 @@ require "rubygems"
|
||||||
# Gems
|
# Gems
|
||||||
require "checkpoint"
|
require "checkpoint"
|
||||||
require "rspec/autorun"
|
require "rspec/autorun"
|
||||||
|
require "webmock/rspec"
|
||||||
|
|
||||||
# Require Vagrant itself so we can reference the proper
|
# Require Vagrant itself so we can reference the proper
|
||||||
# classes to test.
|
# classes to test.
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -968,6 +968,76 @@ VF
|
||||||
end
|
end
|
||||||
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
|
describe "#hook" do
|
||||||
it "should call the action runner with the proper hook" do
|
it "should call the action runner with the proper hook" do
|
||||||
hook_name = :foo
|
hook_name = :foo
|
||||||
|
|
|
@ -189,6 +189,42 @@ describe Vagrant::Plugin::V2::Manager do
|
||||||
expect(instance.provider_configs[:bar]).to eq("bar")
|
expect(instance.provider_configs[:bar]).to eq("bar")
|
||||||
end
|
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
|
it "should enumerate all registered synced folder implementations" do
|
||||||
pA = plugin do |p|
|
pA = plugin do |p|
|
||||||
p.synced_folder("foo") { "bar" }
|
p.synced_folder("foo") { "bar" }
|
||||||
|
|
|
@ -322,6 +322,42 @@ describe Vagrant::Plugin::V2::Plugin do
|
||||||
end
|
end
|
||||||
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
|
describe "synced folders" do
|
||||||
it "should register implementations" do
|
it "should register implementations" do
|
||||||
plugin = Class.new(described_class) do
|
plugin = Class.new(described_class) do
|
||||||
|
|
|
@ -90,6 +90,39 @@ describe Vagrant::Registry do
|
||||||
expect(result["bar"]).to eq("barvalue")
|
expect(result["bar"]).to eq("barvalue")
|
||||||
end
|
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
|
describe "merging" do
|
||||||
it "should merge in another registry" do
|
it "should merge in another registry" do
|
||||||
one = described_class.new
|
one = described_class.new
|
||||||
|
|
|
@ -23,8 +23,10 @@ Gem::Specification.new do |s|
|
||||||
s.add_dependency "hashicorp-checkpoint", "~> 0.1.1"
|
s.add_dependency "hashicorp-checkpoint", "~> 0.1.1"
|
||||||
s.add_dependency "log4r", "~> 1.1.9", "< 1.1.11"
|
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-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 "net-scp", "~> 1.1.0"
|
||||||
s.add_dependency "rb-kqueue", "~> 0.2.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 "wdm", "~> 0.1.0"
|
||||||
s.add_dependency "winrm", "~> 1.1.3"
|
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 "rake"
|
||||||
s.add_development_dependency "rspec", "~> 2.14.0"
|
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
|
# 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
|
# in the gem. It does this by reading all the files in the directory where
|
||||||
|
|
|
@ -288,6 +288,17 @@
|
||||||
</ul>
|
</ul>
|
||||||
<% end %>
|
<% 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>
|
<li<%= sidebar_current("other") %>><a href="/v2/other/index.html">Other</a></li>
|
||||||
|
|
||||||
<% if sidebar_section == "other" %>
|
<% if sidebar_section == "other" %>
|
||||||
|
|
|
@ -14,7 +14,7 @@ boxes. You can read the documentation on the [vagrant box](/v2/cli/box.html)
|
||||||
command for more information.
|
command for more information.
|
||||||
|
|
||||||
The easiest way to use a box is to add a box from the
|
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.
|
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
|
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
|
## Discovering Boxes
|
||||||
|
|
||||||
The easiest way to find boxes is to look on the
|
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
|
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
|
systems as bases, as well as specialized boxes to get you up and running
|
||||||
quickly with LAMP stacks, Ruby, Python, etc.
|
quickly with LAMP stacks, Ruby, Python, etc.
|
||||||
|
|
|
@ -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
|
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
|
to support versioning, putting multiple providers at a single URL, pushing
|
||||||
updates, analytics, and more, we recommend you add the box to
|
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.
|
You can upload both public and private boxes to this service.
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ Today, there are two different components:
|
||||||
box file and so on.
|
box file and so on.
|
||||||
|
|
||||||
* Box Catalog Metadata - This is a JSON document (typically exchanged
|
* 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
|
that specifies the name of the box, a description, available
|
||||||
versions, available providers, and URLs to the actual box files
|
versions, available providers, and URLs to the actual box files
|
||||||
(next component) for each provider and version. If this catalog
|
(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">
|
<div class="alert alert-block alert-info">
|
||||||
<strong>You don't need to manually make the metadata.</strong> If you
|
<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
|
have an account with <a href="https://atlas.hashicorp.com">HashiCorp's Atlas</a>, you
|
||||||
can create boxes there, and Vagrant Cloud automatically creates
|
can create boxes there, and HashiCorp's Atlas automatically creates
|
||||||
the metadata for you. The format is still documented here.
|
the metadata for you. The format is still documented here.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -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
|
`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
|
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
|
on [HashiCorp's Atlas](https://atlas.hashicorp.com). An easy way to find a box
|
||||||
is to use the url `https://vagrantcloud.com/USER/BOX`. For example, for
|
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
|
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`.
|
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
|
This can check if the box in your current Vagrant environment is outdated
|
||||||
|
|
|
@ -26,10 +26,10 @@ This adds a box with the given address to Vagrant. The address can be
|
||||||
one of three things:
|
one of three things:
|
||||||
|
|
||||||
* A shorthand name from the
|
* 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".
|
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
|
For HTTP, basic authentication is supported and `http_proxy` environmental
|
||||||
variables are respected. HTTPS is also supported.
|
variables are respected. HTTPS is also supported.
|
||||||
|
|
||||||
|
@ -93,8 +93,8 @@ you're not using a catalog).
|
||||||
to be specified.
|
to be specified.
|
||||||
|
|
||||||
<div class="alert alert-block alert-warn">
|
<div class="alert alert-block alert-warn">
|
||||||
<strong>Checksums for versioned boxes or boxes from Vagrant Cloud:</strong>
|
<strong>Checksums for versioned boxes or boxes from HashiCorp's Atlas:</strong>
|
||||||
For boxes from Vagrant Cloud, the checksums are embedded in the metadata
|
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.
|
of the box. The metadata itself is served over TLS and its format is validated.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ sidebar_current: "cli-login"
|
||||||
**Command: `vagrant login`**
|
**Command: `vagrant login`**
|
||||||
|
|
||||||
The login command is used to authenticate with a
|
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
|
necessary if you're accessing protected boxes or using
|
||||||
[Vagrant Share](/v2/share/index.html).
|
[Vagrant Share](/v2/share/index.html).
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,8 @@ $ vagrant box add hashicorp/precise32
|
||||||
```
|
```
|
||||||
|
|
||||||
This will download the box named "hashicorp/precise32" from
|
This will download the box named "hashicorp/precise32" from
|
||||||
[Vagrant Cloud](https://vagrantcloud.com), a place where you can find
|
[HashiCorp's Atlas](https://atlas.hashicorp.com), a place where you can find
|
||||||
and host boxes. While it is easiest to download boxes from Vagrant Cloud
|
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.
|
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
|
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
|
this getting started guide, the first question you'll probably have is
|
||||||
"where do I find more boxes?"
|
"where do I find more boxes?"
|
||||||
|
|
||||||
The best place to find more boxes is [Vagrant Cloud](https://vagrantcloud.com).
|
The best place to find more boxes is [HashiCorp's Atlas](https://atlas.hashicorp.com).
|
||||||
Vagrant Cloud has a public directory of freely available boxes that
|
HashiCorp's Atlas has a public directory of freely available boxes that
|
||||||
run various platforms and technologies. Vagrant Cloud also has a great search
|
run various platforms and technologies. HashiCorp's Atlas also has a great search
|
||||||
feature to allow you to find the box you care about.
|
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
|
boxes, as well as private boxes if you intend on creating boxes for your
|
||||||
own organization.
|
own organization.
|
||||||
|
|
|
@ -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
|
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.
|
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
|
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`:
|
Once you have an account, log in using `vagrant login`:
|
||||||
|
|
||||||
|
|
|
@ -17,5 +17,5 @@ admin rights. Vagrant will show you an error if it doesn't have the proper
|
||||||
permissions.
|
permissions.
|
||||||
|
|
||||||
Boxes for Hyper-V can be easily found on
|
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.
|
want to try the `hashicorp/precise64` box.
|
||||||
|
|
|
@ -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"
|
|
@ -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
|
||||||
|
```
|
|
@ -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"
|
|
@ -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"
|
|
@ -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
|
||||||
|
```
|
|
@ -34,4 +34,4 @@ to the left. We also have a section where we go into detail about the
|
||||||
security implications of this feature.
|
security implications of this feature.
|
||||||
|
|
||||||
Vagrant Share requires an account with
|
Vagrant Share requires an account with
|
||||||
[Vagrant Cloud](https://vagrantcloud.com) to be used.
|
[HashiCorp's Atlas](https://atlas.hashicorp.com) to be used.
|
||||||
|
|
|
@ -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
|
`config.vm.box` - This configures what [box](/v2/boxes.html) the
|
||||||
machine will be brought up against. The value here should be the name
|
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
|
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>
|
<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
|
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
|
will tell the user. By default this is true. Updates will only be checked
|
||||||
for boxes that properly support updates (boxes from
|
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).
|
or some other versioned box).
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
@ -74,7 +74,7 @@ URL, then SSL certs will be verified.
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
`config.vm.box_url` - The URL that the configured box can be found at.
|
`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
|
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
|
point to the proper place where the box can be found if it isn't
|
||||||
installed.
|
installed.
|
||||||
|
|
Loading…
Reference in New Issue