Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Jonathan LaBroad 2017-09-11 09:15:22 -04:00
commit 6f53c3991c
70 changed files with 2637 additions and 362 deletions

View File

@ -4,12 +4,54 @@ FEATURES:
IMPROVEMENTS: IMPROVEMENTS:
- commands/ssh-config: Properly display windows path if invoked from msys2 or cygwin [GH-8915] BUG FIXES:
- providers/salt: Remove duplicate stdout, stderr output from salt [GH-8767]
## 2.0.0 (September 7, 2017)
IMPROVEMENTS:
- commands/login: Add support for two-factor authentication [GH-8935]
- commands/ssh-config: Properly display windows path if invoked from msys2 or
cygwin [GH-8915]
- guests/alt: Add support for ALT Linux [GH-8746]
- guests/kali: Fix file permissions on guest plugin ruby files [GH-8950]
- hosts/linux: Provide common systemd detection for services interaction, fix NFS
host interactions [GH-8938]
- providers/salt: Remove duplicate stdout, stderr output from salt [GH-8767]
- providers/salt: Introduce salt_call_args and salt_args option for salt provisioner
[GH-8927]
- providers/virtualbox: Improving resilience of some VirtualBox commands [GH-8951]
- provisioners/ansible(both): Add the compatibility_mode option, with auto-detection
enabled by default [GH-8913, GH-6570]
- provisioners/ansible: Add the version option to the host-based provisioner
[GH-8913, GH-8914]
- provisioners/ansible(both): Add the become and become_user options with deprecation
of sudo and sudo_user options [GH-8913, GH-6570]
- provisioners/ansible: Add the ask_become_pass option with deprecation of the
ask_sudo_pass option [GH-8913, GH-6570]
BUG FIXES: BUG FIXES:
- guests/shell_expand_guest_path : Properly expand guest paths that include relative path alias [GH-8918] - guests/shell_expand_guest_path : Properly expand guest paths that include relative
path alias [GH-8918]
- hosts/linux: Remove duplicate export folders before writing /etc/exports [GH-8945]
- provisioners/ansible(both): Add single quotes to the inventory host variables, only
when necessary [GH-8597]
- provisioners/ansible(both): Add the "all:vars" section to the inventory when defined
in `groups` option [GH-7730]
- provisioners/ansible_local: Extra variables are no longer truncated when a dollar ($)
character is present [GH-7735]
- provisioners/file: Align file provisioner functionality on all platforms [GH-8939]
- util/ssh: Properly quote key path for IdentityFile option to allow for spaces [GH-8924]
BREAKING CHANGES:
- Both Ansible provisioners are now capable of automatically setting the compatibility_mode that
best fits with the Ansible version in use. You may encounter some compatibility issues when
upgrading. If you were using Ansible 2.x and referring to the _ssh-prefixed variables present
in the generated inventory (e.g. `ansible_ssh_host`). In this case, you can fix your Vagrant
setup by setting compatibility_mode = "1.8", or by migrating to the new variable names (e.g.
ansible_host).
## 1.9.8 (August 23, 2017) ## 1.9.8 (August 23, 2017)

View File

@ -468,6 +468,10 @@ module Vagrant
error_key(:nfs_bad_exports) error_key(:nfs_bad_exports)
end end
class NFSDupePerms < VagrantError
error_key(:nfs_dupe_permissions)
end
class NFSExportsFailed < VagrantError class NFSExportsFailed < VagrantError
error_key(:nfs_exports_failed) error_key(:nfs_exports_failed)
end end

View File

@ -8,7 +8,7 @@ module Vagrant
## systemd helpers ## systemd helpers
# systemd is in used # systemd is in use
# #
# @return [Boolean] # @return [Boolean]
def systemd?(comm) def systemd?(comm)

View File

@ -448,6 +448,19 @@ module Vagrant
end end
end end
# systemd is in use
def systemd?
if !defined?(@_systemd)
if !windows?
result = Vagrant::Util::Subprocess.execute("ps", "-o", "comm=", "1")
@_systemd = result.stdout.chomp == "systemd"
else
@_systemd = false
end
end
@_systemd
end
# @private # @private
# Reset the cached values for platform. This is not considered a public # Reset the cached values for platform. This is not considered a public
# API and should only be used for testing. # API and should only be used for testing.

View File

@ -139,7 +139,8 @@ module Vagrant
# Use '-o' instead of '-i' because '-i' does not call # Use '-o' instead of '-i' because '-i' does not call
# percent_expand in misc.c, but '-o' does. when passing the path, # percent_expand in misc.c, but '-o' does. when passing the path,
# replace '%' in the path with '%%' to escape the '%' # replace '%' in the path with '%%' to escape the '%'
command_options += ["-o", "IdentityFile=%s" % [path.to_s.gsub('%', '%%')]] path = path.to_s.gsub('%', '%%')
command_options += ["-o", "IdentityFile=\"#{path}\""]
end end
end end

View File

@ -5,8 +5,15 @@ require "vagrant/util/presence"
module VagrantPlugins module VagrantPlugins
module LoginCommand module LoginCommand
class Client class Client
APP = "app".freeze
include Vagrant::Util::Presence include Vagrant::Util::Presence
attr_accessor :username_or_email
attr_accessor :password
attr_reader :two_factor_default_delivery_method
attr_reader :two_factor_delivery_methods
# Initializes a login client with the given Vagrant::Environment. # Initializes a login client with the given Vagrant::Environment.
# #
# @param [Vagrant::Environment] env # @param [Vagrant::Environment] env
@ -35,29 +42,67 @@ module VagrantPlugins
RestClient.get(url, content_type: :json) RestClient.get(url, content_type: :json)
true true
end end
rescue Errors::Unauthorized
false
end end
# Login logs a user in and returns the token for that user. The token # Login logs a user in and returns the token for that user. The token
# is _not_ stored unless {#store_token} is called. # is _not_ stored unless {#store_token} is called.
# #
# @param [String] username_or_email
# @param [String] password
# @param [String] description # @param [String] description
# @param [String] code
# @return [String] token The access token, or nil if auth failed. # @return [String] token The access token, or nil if auth failed.
def login(username_or_email, password, description: nil) def login(description: nil, code: nil)
@logger.info("Logging in '#{username_or_email}'") @logger.info("Logging in '#{username_or_email}'")
with_error_handling do response = post(
url = "#{Vagrant.server_url}/api/v1/authenticate" "/api/v1/authenticate", {
request = {
user: { user: {
login: username_or_email, login: username_or_email,
password: password password: password
}, },
token: { token: {
description: description description: description
},
two_factor: {
code: code
} }
} }
)
response["token"]
end
# Requests a 2FA code
# @param [String] delivery_method
def request_code(delivery_method)
@env.ui.warn("Requesting 2FA code via #{delivery_method.upcase}...")
response = post(
"/api/v1/two-factor/request-code", {
user: {
login: username_or_email,
password: password
},
two_factor: {
delivery_method: delivery_method.downcase
}
}
)
two_factor = response['two_factor']
obfuscated_destination = two_factor['obfuscated_destination']
@env.ui.success("2FA code sent to #{obfuscated_destination}.")
end
# Issues a post to a Vagrant Cloud path with the given payload.
# @param [String] path
# @param [Hash] payload
# @return [Hash] response data
def post(path, payload)
with_error_handling do
url = File.join(Vagrant.server_url, path)
proxy = nil proxy = nil
proxy ||= ENV["HTTPS_PROXY"] || ENV["https_proxy"] proxy ||= ENV["HTTPS_PROXY"] || ENV["https_proxy"]
@ -67,7 +112,7 @@ module VagrantPlugins
response = RestClient::Request.execute( response = RestClient::Request.execute(
method: :post, method: :post,
url: url, url: url,
payload: JSON.dump(request), payload: JSON.dump(payload),
proxy: proxy, proxy: proxy,
headers: { headers: {
accept: :json, accept: :json,
@ -76,8 +121,7 @@ module VagrantPlugins
}, },
) )
data = JSON.load(response.to_s) JSON.load(response.to_s)
data["token"]
end end
end end
@ -138,14 +182,33 @@ EOH
yield yield
rescue RestClient::Unauthorized rescue RestClient::Unauthorized
@logger.debug("Unauthorized!") @logger.debug("Unauthorized!")
false raise Errors::Unauthorized
rescue RestClient::BadRequest => e
@logger.debug("Bad request:")
@logger.debug(e.message)
@logger.debug(e.backtrace.join("\n"))
parsed_response = JSON.parse(e.response)
errors = parsed_response["errors"].join("\n")
raise Errors::ServerError, errors: errors
rescue RestClient::NotAcceptable => e rescue RestClient::NotAcceptable => e
@logger.debug("Got unacceptable response:") @logger.debug("Got unacceptable response:")
@logger.debug(e.message) @logger.debug(e.message)
@logger.debug(e.backtrace.join("\n")) @logger.debug(e.backtrace.join("\n"))
parsed_response = JSON.parse(e.response)
if two_factor = parsed_response['two_factor']
store_two_factor_information two_factor
if two_factor_default_delivery_method != APP
request_code two_factor_default_delivery_method
end
raise Errors::TwoFactorRequired
end
begin begin
errors = JSON.parse(e.response)["errors"].join("\n") errors = parsed_response["errors"].join("\n")
raise Errors::ServerError, errors: errors raise Errors::ServerError, errors: errors
rescue JSON::ParserError; end rescue JSON::ParserError; end
@ -158,6 +221,33 @@ EOH
def token_path def token_path
@env.data_dir.join("vagrant_login_token") @env.data_dir.join("vagrant_login_token")
end end
def store_two_factor_information(two_factor)
@two_factor_default_delivery_method =
two_factor['default_delivery_method']
@two_factor_delivery_methods =
two_factor['delivery_methods']
@env.ui.warn "2FA is enabled for your account."
if two_factor_default_delivery_method == APP
@env.ui.info "Enter the code from your authenticator."
else
@env.ui.info "Default method is " \
"'#{two_factor_default_delivery_method}'."
end
other_delivery_methods =
two_factor_delivery_methods - [APP]
if other_delivery_methods.any?
other_delivery_methods_sentence = other_delivery_methods
.map { |word| "'#{word}'" }
.join(' or ')
@env.ui.info "You can also type #{other_delivery_methods_sentence} " \
"to request a new code."
end
end
end end
end end
end end

View File

@ -17,6 +17,10 @@ module VagrantPlugins
options[:check] = c options[:check] = c
end end
o.on("-d", "--description DESCRIPTION", String, "Description for the Vagrant Cloud token") do |t|
options[:description] = t
end
o.on("-k", "--logout", "Logs you out if you're logged in") do |k| o.on("-k", "--logout", "Logs you out if you're logged in") do |k|
options[:logout] = k options[:logout] = k
end end
@ -24,6 +28,10 @@ module VagrantPlugins
o.on("-t", "--token TOKEN", String, "Set the Vagrant Cloud token") do |t| o.on("-t", "--token TOKEN", String, "Set the Vagrant Cloud token") do |t|
options[:token] = t options[:token] = t
end end
o.on("-u", "--username USERNAME_OR_EMAIL", String, "Specify your Vagrant Cloud username or email address") do |t|
options[:login] = t
end
end end
# Parse the options # Parse the options
@ -31,6 +39,7 @@ module VagrantPlugins
return if !argv return if !argv
@client = Client.new(@env) @client = Client.new(@env)
@client.username_or_email = options[:login]
# Determine what task we're actually taking based on flags # Determine what task we're actually taking based on flags
if options[:check] if options[:check]
@ -50,28 +59,44 @@ module VagrantPlugins
end end
# Ask for the username # Ask for the username
login = nil if @client.username_or_email
password = nil @env.ui.output("Vagrant Cloud username or email: #{@client.username_or_email}")
description = nil end
while !login until @client.username_or_email
login = @env.ui.ask("Vagrant Cloud Username: ") @client.username_or_email = @env.ui.ask("Vagrant Cloud username or email: ")
end end
while !password until @client.password
password = @env.ui.ask("Password (will be hidden): ", echo: false) @client.password = @env.ui.ask("Password (will be hidden): ", echo: false)
end end
description_default = "Vagrant login from #{Socket.gethostname}" description = options[:description]
while !description if description
description = @env.ui.output("Token description: #{description}")
@env.ui.ask("Token description (Defaults to #{description_default.inspect}): ") else
description_default = "Vagrant login from #{Socket.gethostname}"
until description
description =
@env.ui.ask("Token description (Defaults to #{description_default.inspect}): ")
end
description = description_default if description.empty?
end end
description = description_default if description.empty?
token = @client.login(login, password, description: description) code = nil
if !token
@env.ui.error(I18n.t("login_command.invalid_login")) begin
return 1 token = @client.login(description: description, code: code)
rescue Errors::TwoFactorRequired
until code
code = @env.ui.ask("2FA code: ")
if @client.two_factor_delivery_methods.include?(code.downcase)
delivery_method, code = code, nil
@client.request_code delivery_method
end
end
retry
end end
@client.store_token(token) @client.store_token(token)

View File

@ -12,6 +12,13 @@ module VagrantPlugins
class ServerUnreachable < Error class ServerUnreachable < Error
error_key(:server_unreachable) error_key(:server_unreachable)
end end
class Unauthorized < Error
error_key(:unauthorized)
end
class TwoFactorRequired < Error
end
end end
end end
end end

View File

@ -2,13 +2,16 @@ en:
login_command: login_command:
errors: errors:
server_error: |- server_error: |-
The Vagrant Cloud server responded with an not-OK response: The Vagrant Cloud server responded with a not-OK response:
%{errors} %{errors}
server_unreachable: |- server_unreachable: |-
The Vagrant Cloud server is not currently accepting connections. Please check The Vagrant Cloud server is not currently accepting connections. Please check
your network connection and try again later. your network connection and try again later.
unauthorized: |-
Invalid username or password. Please try again.
check_logged_in: |- check_logged_in: |-
You are already logged in. You are already logged in.
check_not_logged_in: |- check_not_logged_in: |-

View File

@ -83,9 +83,22 @@ module VagrantPlugins
raise_winrm_exception(e, "run_wql", query) raise_winrm_exception(e, "run_wql", query)
end end
# @param from [Array<String>, String] a single path or folder, or an
# array of paths and folders to upload to the guest
# @param to [String] a path or folder on the guest to upload to
# @return [FixNum] Total size transfered from host to guest
def upload(from, to) def upload(from, to)
file_manager = WinRM::FS::FileManager.new(connection) file_manager = WinRM::FS::FileManager.new(connection)
file_manager.upload(from, to) if from.is_a?(Array)
# Preserve return FixNum of bytes transfered
return_bytes = 0
from.each do |file|
return_bytes += file_manager.upload(file, to)
end
return return_bytes
else
file_manager.upload(from, to)
end
end end
def download(from, to) def download(from, to)

View File

@ -0,0 +1,46 @@
module VagrantPlugins
module GuestALT
module Cap
class ChangeHostName
def self.change_host_name(machine, name)
comm = machine.communicate
if !comm.test("hostname -f | grep '^#{name}$'", sudo: false)
basename = name.split('.', 2)[0]
comm.sudo <<-EOH.gsub(/^ {14}/, '')
# Save current hostname saved in /etc/hosts
CURRENT_HOSTNAME_FULL="$(hostname -f)"
CURRENT_HOSTNAME_SHORT="$(hostname -s)"
# New hostname to be saved in /etc/hosts
NEW_HOSTNAME_FULL='#{name}'
NEW_HOSTNAME_SHORT="${NEW_HOSTNAME_FULL%%.*}"
# Update sysconfig
sed -i 's/\\(HOSTNAME=\\).*/\\1#{name}/' /etc/sysconfig/network
# Set the hostname - use hostnamectl if available
if command -v hostnamectl; then
hostnamectl set-hostname --static '#{name}'
hostnamectl set-hostname --transient '#{name}'
else
hostname '#{name}'
fi
# Update ourselves in /etc/hosts
if grep -w "$CURRENT_HOSTNAME_FULL" /etc/hosts; then
sed -i -e "s/\(\s\)$CURRENT_HOSTNAME_FULL\(\s\)/\1$NEW_HOSTNAME_FULL\2/g" -e "s/\(\s\)$CURRENT_HOSTNAME_FULL$/\1$NEW_HOSTNAME_FULL/g" /etc/hosts
fi
if grep -w "$CURRENT_HOSTNAME_SHORT" /etc/hosts; then
sed -i -e "s/\(\s\)$CURRENT_HOSTNAME_SHORT\(\s\)/\1$NEW_HOSTNAME_SHORT\2/g" -e "s/\(\s\)$CURRENT_HOSTNAME_SHORT$/\1$NEW_HOSTNAME_SHORT/g" /etc/hosts
fi
# Restart network
service network restart
EOH
end
end
end
end
end
end

View File

@ -0,0 +1,126 @@
require "tempfile"
require_relative "../../../../lib/vagrant/util/template_renderer"
module VagrantPlugins
module GuestALT
module Cap
class ConfigureNetworks
include Vagrant::Util
extend Vagrant::Util::GuestInspection::Linux
def self.configure_networks(machine, networks)
comm = machine.communicate
network_scripts_dir = machine.guest.capability(:network_scripts_dir)
commands = {:start => [], :middle => [], :end => []}
interfaces = machine.guest.capability(:network_interfaces)
# Check if NetworkManager is installed on the system
nmcli_installed = nmcli?(comm)
networks.each.with_index do |network, i|
network[:device] = interfaces[network[:interface]]
extra_opts = machine.config.vm.networks[i].last.dup
if nmcli_installed
# Now check if the device is actively being managed by NetworkManager
nm_controlled = nm_controlled?(comm, network[:device])
end
if !extra_opts.key?(:nm_controlled)
extra_opts[:nm_controlled] = !!nm_controlled
end
extra_opts[:nm_controlled] = case extra_opts[:nm_controlled]
when true
"yes"
when false, nil
"no"
else
extra_opts[:nm_controlled].to_s
end
if extra_opts[:nm_controlled] == "yes" && !nmcli_installed
raise Vagrant::Errors::NetworkManagerNotInstalled, device: network[:device]
end
# Render a new configuration
template_options = network.merge(extra_opts)
# ALT expects netmasks to be in the CIDR notation, but users may
# specify IPV4 netmasks like "255.255.255.0". This magic converts
# the netmask to the proper value.
if template_options[:netmask] && template_options[:netmask].to_s.include?(".")
template_options[:netmask] = (32-Math.log2((IPAddr.new(template_options[:netmask], Socket::AF_INET).to_i^0xffffffff)+1)).to_i
end
options_entry = TemplateRenderer.render("guests/alt/network_#{network[:type]}", options: template_options)
# Upload the new configuration
options_remote_path = "/tmp/vagrant-network-entry-#{network[:device]}-#{Time.now.to_i}-#{i}"
ipv4_address_remote_path = "/tmp/vagrant-network-ipv4-address-entry-#{network[:device]}-#{Time.now.to_i}-#{i}"
ipv4_route_remote_path = "/tmp/vagrant-network-ipv4-route-entry-#{network[:device]}-#{Time.now.to_i}-#{i}"
Tempfile.open("vagrant-alt-configure-networks") do |f|
f.binmode
f.write(options_entry)
f.fsync
f.close
machine.communicate.upload(f.path, options_remote_path)
end
# Add the new interface and bring it back up
iface_path = "#{network_scripts_dir}/ifaces/#{network[:device]}"
if network[:type].to_sym == :static
ipv4_address_entry = TemplateRenderer.render("guests/alt/network_ipv4address", options: template_options)
# Upload the new ipv4address configuration
Tempfile.open("vagrant-alt-configure-ipv4-address") do |f|
f.binmode
f.write(ipv4_address_entry)
f.fsync
f.close
machine.communicate.upload(f.path, ipv4_address_remote_path)
end
ipv4_route_entry = TemplateRenderer.render("guests/alt/network_ipv4route", options: template_options)
# Upload the new ipv4route configuration
Tempfile.open("vagrant-alt-configure-ipv4-route") do |f|
f.binmode
f.write(ipv4_route_entry)
f.fsync
f.close
machine.communicate.upload(f.path, ipv4_route_remote_path)
end
end
if nm_controlled and extra_opts[:nm_controlled] == "yes"
commands[:start] << "nmcli d disconnect iface '#{network[:device]}'"
else
commands[:start] << "/sbin/ifdown '#{network[:device]}'"
end
commands[:middle] << "mkdir -p '#{iface_path}'"
commands[:middle] << "mv -f '#{options_remote_path}' '#{iface_path}/options'"
if network[:type].to_sym == :static
commands[:middle] << "mv -f '#{ipv4_address_remote_path}' '#{iface_path}/ipv4address'"
commands[:middle] << "mv -f '#{ipv4_route_remote_path}' '#{iface_path}/ipv4route'"
end
if extra_opts[:nm_controlled] == "no"
commands[:end] << "/sbin/ifup '#{network[:device]}'"
end
end
if nmcli_installed
commands[:middle] << "((systemctl | grep NetworkManager.service) && systemctl restart NetworkManager) || " \
"(test -f /etc/init.d/NetworkManager && /etc/init.d/NetworkManager restart)"
end
commands = commands[:start] + commands[:middle] + commands[:end]
comm.sudo(commands.join("\n"))
comm.wait_for_ready(5)
end
end
end
end
end

View File

@ -0,0 +1,63 @@
module VagrantPlugins
module GuestALT
module Cap
class Flavor
def self.flavor(machine)
comm = machine.communicate
# Read the version file
if comm.test("test -f /etc/os-release")
name = nil
comm.sudo("grep NAME /etc/os-release") do |type, data|
if type == :stdout
name = data.split("=")[1].gsub!(/\A"|"\Z/, '')
end
end
if !name.nil? and name == "Sisyphus"
return :alt
end
version = nil
comm.sudo("grep VERSION_ID /etc/os-release") do |type, data|
if type == :stdout
verstr = data.split("=")[1]
if verstr == "p8"
version = 8
elsif verstr =~ /^[[\d]]/
version = verstr.chomp.to_i
subversion = verstr.chomp.split(".")[1].to_i
if subversion > 90
version += 1
end
end
end
end
if version.nil? or version == 0
return :alt
else
return :"alt_#{version}"
end
else
output = ""
comm.sudo("cat /etc/altlinux-release") do |_, data|
output = data
end
# Detect various flavors we care about
if output =~ /(ALT SP|ALT Education|ALT Workstation|ALT Workstation K|ALT Linux starter kit)\s*8(\.[1-9])?( .+)?/i
return :alt_8
elsif output =~ /ALT\s+8(\.[1-9])?( .+)?\s.+/i
return :alt_8
elsif output =~ /ALT Linux p8( .+)?/i
return :alt_8
else
return :alt
end
end
end
end
end
end
end

View File

@ -0,0 +1,11 @@
module VagrantPlugins
module GuestALT
module Cap
class NetworkScriptsDir
def self.network_scripts_dir(machine)
"/etc/net"
end
end
end
end
end

View File

@ -0,0 +1,13 @@
module VagrantPlugins
module GuestALT
module Cap
class RSync
def self.rsync_install(machine)
machine.communicate.sudo <<-EOH.gsub(/^ {12}/, '')
apt-get install -y -qq install rsync
EOH
end
end
end
end
end

View File

@ -0,0 +1,9 @@
module VagrantPlugins
module GuestALT
class Guest < Vagrant.plugin("2", :guest)
def detect?(machine)
machine.communicate.test("cat /etc/altlinux-release")
end
end
end
end

View File

@ -0,0 +1,40 @@
require "vagrant"
module VagrantPlugins
module GuestALT
class Plugin < Vagrant.plugin("2")
name "ALT Platform guest"
description "ALT Platform guest support."
guest(:alt, :redhat) do
require_relative "guest"
Guest
end
guest_capability(:alt, :change_host_name) do
require_relative "cap/change_host_name"
Cap::ChangeHostName
end
guest_capability(:alt, :configure_networks) do
require_relative "cap/configure_networks"
Cap::ConfigureNetworks
end
guest_capability(:alt, :flavor) do
require_relative "cap/flavor"
Cap::Flavor
end
guest_capability(:alt, :network_scripts_dir) do
require_relative "cap/network_scripts_dir"
Cap::NetworkScriptsDir
end
guest_capability(:alt, :rsync_install) do
require_relative "cap/rsync"
Cap::RSync
end
end
end
end

0
plugins/guests/kali/guest.rb Executable file → Normal file
View File

0
plugins/guests/kali/plugin.rb Executable file → Normal file
View File

View File

@ -0,0 +1,43 @@
require "vagrant/util/subprocess"
require "vagrant/util/which"
module VagrantPlugins
module HostALT
module Cap
class NFS
def self.nfs_check_command(env)
if systemd?
return "systemctl status --no-pager nfs-server.service"
else
return "/etc/init.d/nfs status"
end
end
def self.nfs_start_command(env)
if systemd?
return "systemctl start rpcbind nfs-server.service"
else
return "/etc/init.d/nfs restart"
end
end
def self.nfs_installed(environment)
if systemd?
system("systemctl --no-pager --no-legend --plain list-unit-files --all --type=service | grep --fixed-strings --quiet nfs-server.service")
else
system("rpm -q nfs-server --quiet 2>&1")
end
end
protected
# This tests to see if systemd is used on the system. This is used
# in newer versions of ALT, and requires a change in behavior.
def self.systemd?
result = Vagrant::Util::Subprocess.execute("ps", "-o", "comm=", "1")
return result.stdout.chomp == "systemd"
end
end
end
end
end

11
plugins/hosts/alt/host.rb Normal file
View File

@ -0,0 +1,11 @@
require "vagrant"
module VagrantPlugins
module HostALT
class Host < Vagrant.plugin("2", :host)
def detect?(env)
File.exist?("/etc/altlinux-release")
end
end
end
end

View File

@ -0,0 +1,32 @@
require "vagrant"
module VagrantPlugins
module HostALT
class Plugin < Vagrant.plugin("2")
name "ALT Platform host"
description "ALT Platform host support."
host("alt", "linux") do
require_relative "host"
Host
end
host_capability("alt", "nfs_installed") do
require_relative "cap/nfs"
Cap::NFS
end
# Linux-specific helpers we need to determine paths that can
# be overriden.
host_capability("alt", "nfs_check_command") do
require_relative "cap/nfs"
Cap::NFS
end
host_capability("alt", "nfs_start_command") do
require_relative "cap/nfs"
Cap::NFS
end
end
end
end

View File

@ -6,30 +6,23 @@ module VagrantPlugins
module Cap module Cap
class NFS class NFS
def self.nfs_check_command(env) def self.nfs_check_command(env)
if systemd? if Vagrant::Util::Platform.systemd?
return "#{systemctl_path} status --no-pager nfs-server.service" "#{systemctl_path} status --no-pager nfs-server.service"
else else
return "/etc/init.d/nfs status" "/etc/init.d/nfs status"
end end
end end
def self.nfs_start_command(env) def self.nfs_start_command(env)
if systemd? if Vagrant::Util::Platform.systemd?
return "#{systemctl_path} start rpcbind nfs-server.service" "#{systemctl_path} start rpcbind nfs-server.service"
else else
return "/etc/init.d/nfs restart" "/etc/init.d/nfs restart"
end end
end end
protected protected
# This tests to see if systemd is used on the system. This is used
# in newer versions of Arch, and requires a change in behavior.
def self.systemd?
result = Vagrant::Util::Subprocess.execute("ps", "-o", "comm=", "1")
return result.stdout.chomp == "systemd"
end
def self.systemctl_path def self.systemctl_path
path = Vagrant::Util::Which.which("systemctl") path = Vagrant::Util::Which.which("systemctl")
return path if path return path if path

View File

@ -1,3 +1,4 @@
require "shellwords"
require "vagrant/util" require "vagrant/util"
require "vagrant/util/shell_quote" require "vagrant/util/shell_quote"
require "vagrant/util/retryable" require "vagrant/util/retryable"
@ -15,11 +16,19 @@ module VagrantPlugins
end end
def self.nfs_check_command(env) def self.nfs_check_command(env)
"/etc/init.d/nfs-kernel-server status" if Vagrant::Util::Platform.systemd?
"systemctl status --no-pager nfs-server.service"
else
"/etc/init.d/nfs-kernel-server status"
end
end end
def self.nfs_start_command(env) def self.nfs_start_command(env)
"/etc/init.d/nfs-kernel-server start" if Vagrant::Util::Platform.systemd?
"systemctl start nfs-server.service"
else
"/etc/init.d/nfs-kernel-server start"
end
end end
def self.nfs_export(env, ui, id, ips, folders) def self.nfs_export(env, ui, id, ips, folders)
@ -29,6 +38,7 @@ module VagrantPlugins
nfs_start_command = env.host.capability(:nfs_start_command) nfs_start_command = env.host.capability(:nfs_start_command)
nfs_opts_setup(folders) nfs_opts_setup(folders)
folders = folder_dupe_check(folders)
output = Vagrant::Util::TemplateRenderer.render('nfs/exports_linux', output = Vagrant::Util::TemplateRenderer.render('nfs/exports_linux',
uuid: id, uuid: id,
ips: ips, ips: ips,
@ -43,16 +53,20 @@ module VagrantPlugins
nfs_write_exports(output) nfs_write_exports(output)
if nfs_running?(nfs_check_command) if nfs_running?(nfs_check_command)
system("sudo #{nfs_apply_command}") Vagrant::Util::Subprocess.execute("sudo", *Shellwords.split(nfs_apply_command)).exit_code == 0
else else
system("sudo #{nfs_start_command}") Vagrant::Util::Subprocess.execute("sudo", *Shellwords.split(nfs_start_command)).exit_code == 0
end end
end end
def self.nfs_installed(environment) def self.nfs_installed(environment)
retryable(tries: 10, on: TypeError) do if Vagrant::Util::Platform.systemd?
# Check procfs to see if NFSd is a supported filesystem Vagrant::Util::Subprocess.execute("/bin/sh", "-c",
system("cat /proc/filesystems | grep nfsd > /dev/null 2>&1") "systemctl --no-pager --no-legend --plain list-unit-files --all --type=service " \
"| grep nfs-server.service").exit_code == 0
else
Vagrant::Util::Subprocess.execute("modinfo", "nfsd").exit_code == 0 ||
Vagrant::Util::Subprocess.execute("grep", "nfsd", "/proc/filesystems").exit_code == 0
end end
end end
@ -84,6 +98,37 @@ module VagrantPlugins
protected protected
# Takes a hash of folders and removes any duplicate exports that
# share the same hostpath to avoid duplicate entries in /etc/exports
# ref: GH-4666
def self.folder_dupe_check(folders)
return_folders = {}
# Group by hostpath to see if there are multiple exports coming
# from the same folder
export_groups = folders.values.group_by { |h| h[:hostpath] }
# We need to check that each group key only has 1 value,
# and if not, check each nfs option. If all nfs options are the same
# we're good, otherwise throw an exception
export_groups.each do |path,group|
if group.size > 1
# if the linux nfs options aren't all the same throw an exception
group1_opts = group.first[:linux__nfs_options]
if !group.all? {|g| g[:linux__nfs_options] == group1_opts}
raise Vagrant::Errors::NFSDupePerms, hostpath: group.first[:hostpath]
else
# if they're the same just pick the first one
return_folders[path] = group.first
end
else
# just return folder, there are no duplicates
return_folders[path] = group.first
end
end
return_folders
end
def self.nfs_cleanup(remove_ids) def self.nfs_cleanup(remove_ids)
return if !File.exist?(NFS_EXPORTS_PATH) return if !File.exist?(NFS_EXPORTS_PATH)
@ -186,7 +231,7 @@ module VagrantPlugins
end end
def self.nfs_running?(check_command) def self.nfs_running?(check_command)
system(check_command) Vagrant::Util::Subprocess.execute(*Shellwords.split(check_command)).exit_code == 0
end end
end end
end end

View File

@ -5,11 +5,19 @@ module VagrantPlugins
module Cap module Cap
class NFS class NFS
def self.nfs_check_command(env) def self.nfs_check_command(env)
"#{nfs_server_binary} status" if Vagrant::Util::Platform.systemd?
"systemctl status --no-pager nfs-server.service"
else
"#{nfs_server_binary} status"
end
end end
def self.nfs_start_command(env) def self.nfs_start_command(env)
"#{nfs_server_binary} start" if Vagrant::Util::Platform.systemd?
"systemctl start nfs-server.service"
else
"#{nfs_server_binary} start"
end
end end
protected protected

View File

@ -85,11 +85,18 @@ module VagrantPlugins
execute("list", "vms").split("\n").each do |line| execute("list", "vms").split("\n").each do |line|
if vm_name = line[/^".+?"\s+\{(.+?)\}$/, 1] if vm_name = line[/^".+?"\s+\{(.+?)\}$/, 1]
info = execute("showvminfo", vm_name, "--machinereadable", retryable: true) begin
info.split("\n").each do |line| info = execute("showvminfo", vm_name, "--machinereadable", retryable: true)
if network_name = line[/^hostonlyadapter\d+="(.+?)"$/, 1] info.split("\n").each do |line|
networks.delete(network_name) if network_name = line[/^hostonlyadapter\d+="(.+?)"$/, 1]
networks.delete(network_name)
end
end end
rescue Vagrant::Errors::VBoxManageError => e
raise if !e.extra_data[:stderr].include?("VBOX_E_OBJECT_NOT_FOUND")
# VirtualBox could not find the vm. It may have been deleted
# by another process after we called 'vboxmanage list vms'? Ignore this error.
end end
end end
end end
@ -422,8 +429,15 @@ module VagrantPlugins
# Ignore our own used ports # Ignore our own used ports
next if uuid == @uuid next if uuid == @uuid
read_forwarded_ports(uuid, true).each do |_, _, hostport, _| begin
ports << hostport read_forwarded_ports(uuid, true).each do |_, _, hostport, _|
ports << hostport
end
rescue Vagrant::Errors::VBoxManageError => e
raise if !e.extra_data[:stderr].include?("VBOX_E_OBJECT_NOT_FOUND")
# VirtualBox could not find the vm. It may have been deleted
# by another process after we called 'vboxmanage list vms'? Ignore this error.
end end
end end
end end

View File

@ -176,11 +176,18 @@ module VagrantPlugins
execute("list", "vms").split("\n").each do |line| execute("list", "vms").split("\n").each do |line|
if vm = line[/^".+?"\s+\{(.+?)\}$/, 1] if vm = line[/^".+?"\s+\{(.+?)\}$/, 1]
info = execute("showvminfo", vm, "--machinereadable", retryable: true) begin
info.split("\n").each do |line| info = execute("showvminfo", vm, "--machinereadable", retryable: true)
if adapter = line[/^hostonlyadapter\d+="(.+?)"$/, 1] info.split("\n").each do |line|
networks.delete(adapter) if adapter = line[/^hostonlyadapter\d+="(.+?)"$/, 1]
networks.delete(adapter)
end
end end
rescue Vagrant::Errors::VBoxManageError => e
raise if !e.extra_data[:stderr].include?("VBOX_E_OBJECT_NOT_FOUND")
# VirtualBox could not find the vm. It may have been deleted
# by another process after we called 'vboxmanage list vms'? Ignore this error.
end end
end end
end end
@ -525,8 +532,15 @@ module VagrantPlugins
# Ignore our own used ports # Ignore our own used ports
next if uuid == @uuid next if uuid == @uuid
read_forwarded_ports(uuid, true).each do |_, _, hostport, _| begin
ports << hostport read_forwarded_ports(uuid, true).each do |_, _, hostport, _|
ports << hostport
end
rescue Vagrant::Errors::VBoxManageError => e
raise if !e.extra_data[:stderr].include?("VBOX_E_OBJECT_NOT_FOUND")
# VirtualBox could not find the vm. It may have been deleted
# by another process after we called 'vboxmanage list vms'? Ignore this error.
end end
end end
end end

View File

@ -83,11 +83,18 @@ module VagrantPlugins
execute("list", "vms").split("\n").each do |line| execute("list", "vms").split("\n").each do |line|
if line =~ /^".+?"\s+\{(.+?)\}$/ if line =~ /^".+?"\s+\{(.+?)\}$/
info = execute("showvminfo", $1.to_s, "--machinereadable", retryable: true) begin
info.split("\n").each do |inner_line| info = execute("showvminfo", $1.to_s, "--machinereadable", retryable: true)
if inner_line =~ /^hostonlyadapter\d+="(.+?)"$/ info.split("\n").each do |inner_line|
networks.delete($1.to_s) if inner_line =~ /^hostonlyadapter\d+="(.+?)"$/
networks.delete($1.to_s)
end
end end
rescue Vagrant::Errors::VBoxManageError => e
raise if !e.extra_data[:stderr].include?("VBOX_E_OBJECT_NOT_FOUND")
# VirtualBox could not find the vm. It may have been deleted
# by another process after we called 'vboxmanage list vms'? Ignore this error.
end end
end end
end end
@ -458,8 +465,15 @@ module VagrantPlugins
# Ignore our own used ports # Ignore our own used ports
next if uuid == @uuid next if uuid == @uuid
read_forwarded_ports(uuid, true).each do |_, _, hostport, _| begin
ports << hostport read_forwarded_ports(uuid, true).each do |_, _, hostport, _|
ports << hostport
end
rescue Vagrant::Errors::VBoxManageError => e
raise if !e.extra_data[:stderr].include?("VBOX_E_OBJECT_NOT_FOUND")
# VirtualBox could not find the vm. It may have been deleted
# by another process after we called 'vboxmanage list vms'? Ignore this error.
end end
end end
end end

View File

@ -180,11 +180,18 @@ module VagrantPlugins
execute("list", "vms", retryable: true).split("\n").each do |line| execute("list", "vms", retryable: true).split("\n").each do |line|
if line =~ /^".+?"\s+\{(.+?)\}$/ if line =~ /^".+?"\s+\{(.+?)\}$/
info = execute("showvminfo", $1.to_s, "--machinereadable", retryable: true) begin
info.split("\n").each do |inner_line| info = execute("showvminfo", $1.to_s, "--machinereadable", retryable: true)
if inner_line =~ /^hostonlyadapter\d+="(.+?)"$/ info.split("\n").each do |inner_line|
networks.delete($1.to_s) if inner_line =~ /^hostonlyadapter\d+="(.+?)"$/
networks.delete($1.to_s)
end
end end
rescue Vagrant::Errors::VBoxManageError => e
raise if !e.extra_data[:stderr].include?("VBOX_E_OBJECT_NOT_FOUND")
# VirtualBox could not find the vm. It may have been deleted
# by another process after we called 'vboxmanage list vms'? Ignore this error.
end end
end end
end end
@ -569,8 +576,15 @@ module VagrantPlugins
# Ignore our own used ports # Ignore our own used ports
next if uuid == @uuid next if uuid == @uuid
read_forwarded_ports(uuid, true).each do |_, _, hostport, _| begin
ports << hostport read_forwarded_ports(uuid, true).each do |_, _, hostport, _|
ports << hostport
end
rescue Vagrant::Errors::VBoxManageError => e
raise if !e.extra_data[:stderr].include?("VBOX_E_OBJECT_NOT_FOUND")
# VirtualBox could not find the vm. It may have been deleted
# by another process after we called 'vboxmanage list vms'? Ignore this error.
end end
end end
end end

View File

@ -17,19 +17,23 @@ module VagrantPlugins
end end
def clear_forwarded_ports def clear_forwarded_ports
args = [] retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do
read_forwarded_ports(@uuid).each do |nic, name, _, _| args = []
args.concat(["--natpf#{nic}", "delete", name]) read_forwarded_ports(@uuid).each do |nic, name, _, _|
end args.concat(["--natpf#{nic}", "delete", name])
end
execute("modifyvm", @uuid, *args) if !args.empty? execute("modifyvm", @uuid, *args) if !args.empty?
end
end end
def clear_shared_folders def clear_shared_folders
info = execute("showvminfo", @uuid, "--machinereadable", retryable: true) retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do
info.split("\n").each do |line| info = execute("showvminfo", @uuid, "--machinereadable", retryable: true)
if line =~ /^SharedFolderNameMachineMapping\d+="(.+?)"$/ info.split("\n").each do |line|
execute("sharedfolder", "remove", @uuid, "--name", $1.to_s) if line =~ /^SharedFolderNameMachineMapping\d+="(.+?)"$/
execute("sharedfolder", "remove", @uuid, "--name", $1.to_s)
end
end end
end end
end end
@ -41,22 +45,29 @@ module VagrantPlugins
args += ["--snapshot", snapshot_name, "--options", "link"] args += ["--snapshot", snapshot_name, "--options", "link"]
end end
execute("clonevm", master_id, *args) execute("clonevm", master_id, *args, retryable: true)
return get_machine_id(machine_name) return get_machine_id(machine_name)
end end
def create_dhcp_server(network, options) def create_dhcp_server(network, options)
execute("dhcpserver", "add", "--ifname", network, retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do
"--ip", options[:dhcp_ip], begin
"--netmask", options[:netmask], execute("dhcpserver", "add", "--ifname", network,
"--lowerip", options[:dhcp_lower], "--ip", options[:dhcp_ip],
"--upperip", options[:dhcp_upper], "--netmask", options[:netmask],
"--enable") "--lowerip", options[:dhcp_lower],
"--upperip", options[:dhcp_upper],
"--enable")
rescue Vagrant::Errors::VBoxManageError => e
return if e.extra_data[:stderr] == 'VBoxManage: error: DHCP server already exists'
raise
end
end
end end
def create_host_only_network(options) def create_host_only_network(options)
# Create the interface # Create the interface
execute("hostonlyif", "create") =~ /^Interface '(.+?)' was successfully created$/ execute("hostonlyif", "create", retryable: true) =~ /^Interface '(.+?)' was successfully created$/
name = $1.to_s name = $1.to_s
# Get the IP so we can determine v4 vs v6 # Get the IP so we can determine v4 vs v6
@ -66,11 +77,13 @@ module VagrantPlugins
if ip.ipv4? if ip.ipv4?
execute("hostonlyif", "ipconfig", name, execute("hostonlyif", "ipconfig", name,
"--ip", options[:adapter_ip], "--ip", options[:adapter_ip],
"--netmask", options[:netmask]) "--netmask", options[:netmask],
retryable: true)
elsif ip.ipv6? elsif ip.ipv6?
execute("hostonlyif", "ipconfig", name, execute("hostonlyif", "ipconfig", name,
"--ipv6", options[:adapter_ip], "--ipv6", options[:adapter_ip],
"--netmasklengthv6", options[:netmask].to_s) "--netmasklengthv6", options[:netmask].to_s,
retryable: true)
else else
raise "BUG: Unknown IP type: #{ip.inspect}" raise "BUG: Unknown IP type: #{ip.inspect}"
end end
@ -85,7 +98,7 @@ module VagrantPlugins
end end
def create_snapshot(machine_id, snapshot_name) def create_snapshot(machine_id, snapshot_name)
execute("snapshot", machine_id, "take", snapshot_name) execute("snapshot", machine_id, "take", snapshot_name, retryable: true)
end end
def delete_snapshot(machine_id, snapshot_name) def delete_snapshot(machine_id, snapshot_name)
@ -95,7 +108,7 @@ module VagrantPlugins
yield 0 if block_given? yield 0 if block_given?
# Snapshot and report the % progress # Snapshot and report the % progress
execute("snapshot", machine_id, "delete", snapshot_name) do |type, data| execute("snapshot", machine_id, "delete", snapshot_name, retryable: true) do |type, data|
if type == :stderr if type == :stderr
# Append the data so we can see the full view # Append the data so we can see the full view
total << data.gsub("\r", "") total << data.gsub("\r", "")
@ -142,7 +155,7 @@ module VagrantPlugins
total = "" total = ""
yield 0 if block_given? yield 0 if block_given?
execute("snapshot", machine_id, "restore", snapshot_name) do |type, data| execute("snapshot", machine_id, "restore", snapshot_name, retryable: true) do |type, data|
if type == :stderr if type == :stderr
# Append the data so we can see the full view # Append the data so we can see the full view
total << data.gsub("\r", "") total << data.gsub("\r", "")
@ -165,7 +178,7 @@ module VagrantPlugins
end end
def delete def delete
execute("unregistervm", @uuid, "--delete") execute("unregistervm", @uuid, "--delete", retryable: true)
end end
def delete_unused_host_only_networks def delete_unused_host_only_networks
@ -176,11 +189,18 @@ module VagrantPlugins
execute("list", "vms", retryable: true).split("\n").each do |line| execute("list", "vms", retryable: true).split("\n").each do |line|
if line =~ /^".+?"\s+\{(.+?)\}$/ if line =~ /^".+?"\s+\{(.+?)\}$/
info = execute("showvminfo", $1.to_s, "--machinereadable", retryable: true) begin
info.split("\n").each do |inner_line| info = execute("showvminfo", $1.to_s, "--machinereadable", retryable: true)
if inner_line =~ /^hostonlyadapter\d+="(.+?)"$/ info.split("\n").each do |inner_line|
networks.delete($1.to_s) if inner_line =~ /^hostonlyadapter\d+="(.+?)"$/
networks.delete($1.to_s)
end
end end
rescue Vagrant::Errors::VBoxManageError => e
raise if !e.extra_data[:stderr].include?("VBOX_E_OBJECT_NOT_FOUND")
# VirtualBox could not find the vm. It may have been deleted
# by another process after we called 'vboxmanage list vms'? Ignore this error.
end end
end end
end end
@ -192,12 +212,12 @@ module VagrantPlugins
raw("dhcpserver", "remove", "--ifname", name) raw("dhcpserver", "remove", "--ifname", name)
# Delete the actual host only network interface. # Delete the actual host only network interface.
execute("hostonlyif", "remove", name) execute("hostonlyif", "remove", name, retryable: true)
end end
end end
def discard_saved_state def discard_saved_state
execute("discardstate", @uuid) execute("discardstate", @uuid, retryable: true)
end end
def enable_adapters(adapters) def enable_adapters(adapters)
@ -230,7 +250,7 @@ module VagrantPlugins
end end
end end
execute("modifyvm", @uuid, *args) execute("modifyvm", @uuid, *args, retryable: true)
end end
def execute_command(command) def execute_command(command)
@ -238,7 +258,17 @@ module VagrantPlugins
end end
def export(path) def export(path)
execute("export", @uuid, "--output", path.to_s) retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do
begin
execute("export", @uuid, "--output", path.to_s)
rescue Vagrant::Errors::VBoxManageError => e
raise if !e.extra_data[:stderr].include?("VERR_E_FILE_ERROR")
# If the file already exists we'll throw a custom error
raise Vagrant::Errors::VirtualBoxFileExists,
stderr: e.extra_data[:stderr]
end
end
end end
def forward_ports(ports) def forward_ports(ports)
@ -255,7 +285,7 @@ module VagrantPlugins
pf_builder.join(",")]) pf_builder.join(",")])
end end
execute("modifyvm", @uuid, *args) if !args.empty? execute("modifyvm", @uuid, *args, retryable: true) if !args.empty?
end end
def get_machine_id(machine_name) def get_machine_id(machine_name)
@ -266,7 +296,7 @@ module VagrantPlugins
end end
def halt def halt
execute("controlvm", @uuid, "poweroff") execute("controlvm", @uuid, "poweroff", retryable: true)
end end
def import(ovf) def import(ovf)
@ -315,7 +345,7 @@ module VagrantPlugins
end end
end end
execute("import", ovf , *name_params, *disk_params) do |type, data| execute("import", ovf , *name_params, *disk_params, retryable: true) do |type, data|
if type == :stdout if type == :stdout
# Keep track of the stdout so that we can get the VM name # Keep track of the stdout so that we can get the VM name
output << data output << data
@ -567,9 +597,16 @@ module VagrantPlugins
# Ignore our own used ports # Ignore our own used ports
next if uuid == @uuid next if uuid == @uuid
read_forwarded_ports(uuid, true).each do |_, _, hostport, _, hostip| begin
hostip = '*' if hostip.nil? || hostip.empty? read_forwarded_ports(uuid, true).each do |_, _, hostport, _, hostip|
used_ports[hostport].add?(hostip) hostip = '*' if hostip.nil? || hostip.empty?
used_ports[hostport].add?(hostip)
end
rescue Vagrant::Errors::VBoxManageError => e
raise if !e.extra_data[:stderr].include?("VBOX_E_OBJECT_NOT_FOUND")
# VirtualBox could not find the vm. It may have been deleted
# by another process after we called 'vboxmanage list vms'? Ignore this error.
end end
end end
end end
@ -590,26 +627,30 @@ module VagrantPlugins
def reconfig_host_only(interface) def reconfig_host_only(interface)
execute("hostonlyif", "ipconfig", interface[:name], execute("hostonlyif", "ipconfig", interface[:name],
"--ipv6", interface[:ipv6]) "--ipv6", interface[:ipv6], retryable: true)
end end
def remove_dhcp_server(network_name) def remove_dhcp_server(network_name)
execute("dhcpserver", "remove", "--netname", network_name) execute("dhcpserver", "remove", "--netname", network_name, retryable: true)
end end
def set_mac_address(mac) def set_mac_address(mac)
execute("modifyvm", @uuid, "--macaddress1", mac) execute("modifyvm", @uuid, "--macaddress1", mac, retryable: true)
end end
def set_name(name) def set_name(name)
execute("modifyvm", @uuid, "--name", name, retryable: true) retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do
rescue Vagrant::Errors::VBoxManageError => e begin
raise if !e.extra_data[:stderr].include?("VERR_ALREADY_EXISTS") execute("modifyvm", @uuid, "--name", name)
rescue Vagrant::Errors::VBoxManageError => e
raise if !e.extra_data[:stderr].include?("VERR_ALREADY_EXISTS")
# We got VERR_ALREADY_EXISTS. This means that we're renaming to # We got VERR_ALREADY_EXISTS. This means that we're renaming to
# a VM name that already exists. Raise a custom error. # a VM name that already exists. Raise a custom error.
raise Vagrant::Errors::VirtualBoxNameExists, raise Vagrant::Errors::VirtualBoxNameExists,
stderr: e.extra_data[:stderr] stderr: e.extra_data[:stderr]
end
end
end end
def share_folders(folders) def share_folders(folders)
@ -633,10 +674,10 @@ module VagrantPlugins
args << "--transient" if folder.key?(:transient) && folder[:transient] args << "--transient" if folder.key?(:transient) && folder[:transient]
# Enable symlinks on the shared folder # Enable symlinks on the shared folder
execute("setextradata", @uuid, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{folder[:name]}", "1") execute("setextradata", @uuid, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{folder[:name]}", "1", retryable: true)
# Add the shared folder # Add the shared folder
execute("sharedfolder", "add", @uuid, *args) execute("sharedfolder", "add", @uuid, *args, retryable: true)
end end
end end
@ -658,40 +699,40 @@ module VagrantPlugins
def start(mode) def start(mode)
command = ["startvm", @uuid, "--type", mode.to_s] command = ["startvm", @uuid, "--type", mode.to_s]
r = raw(*command) retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do
r = raw(*command)
if r.exit_code == 0 || r.stdout =~ /VM ".+?" has been successfully started/ if r.exit_code == 0 || r.stdout =~ /VM ".+?" has been successfully started/
# Some systems return an exit code 1 for some reason. For that # Some systems return an exit code 1 for some reason. For that
# we depend on the output. # we depend on the output.
return true return true
end
# If we reached this point then it didn't work out.
raise Vagrant::Errors::VBoxManageError,
command: command.inspect,
stderr: r.stderr
end end
# If we reached this point then it didn't work out.
raise Vagrant::Errors::VBoxManageError,
command: command.inspect,
stderr: r.stderr
end end
def suspend def suspend
execute("controlvm", @uuid, "savestate") execute("controlvm", @uuid, "savestate", retryable: true)
end end
def unshare_folders(names) def unshare_folders(names)
names.each do |name| names.each do |name|
begin retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do
execute( begin
"sharedfolder", "remove", @uuid, execute(
"--name", name, "sharedfolder", "remove", @uuid,
"--transient") "--name", name,
"--transient")
execute( execute(
"setextradata", @uuid, "setextradata", @uuid,
"VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{name}") "VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{name}")
rescue Vagrant::Errors::VBoxManageError => e rescue Vagrant::Errors::VBoxManageError => e
if e.extra_data[:stderr].include?("VBOX_E_FILE_ERROR") raise if !e.extra_data[:stderr].include?("VBOX_E_FILE_ERROR")
# The folder doesn't exist. ignore.
else
raise
end end
end end
end end

View File

@ -1,3 +1,5 @@
require_relative "../constants"
module VagrantPlugins module VagrantPlugins
module Ansible module Ansible
module Config module Config
@ -6,6 +8,9 @@ module VagrantPlugins
GALAXY_COMMAND_DEFAULT = "ansible-galaxy install --role-file=%{role_file} --roles-path=%{roles_path} --force".freeze GALAXY_COMMAND_DEFAULT = "ansible-galaxy install --role-file=%{role_file} --roles-path=%{roles_path} --force".freeze
PLAYBOOK_COMMAND_DEFAULT = "ansible-playbook".freeze PLAYBOOK_COMMAND_DEFAULT = "ansible-playbook".freeze
attr_accessor :become
attr_accessor :become_user
attr_accessor :compatibility_mode
attr_accessor :config_file attr_accessor :config_file
attr_accessor :extra_vars attr_accessor :extra_vars
attr_accessor :galaxy_role_file attr_accessor :galaxy_role_file
@ -20,13 +25,29 @@ module VagrantPlugins
attr_accessor :raw_arguments attr_accessor :raw_arguments
attr_accessor :skip_tags attr_accessor :skip_tags
attr_accessor :start_at_task attr_accessor :start_at_task
attr_accessor :sudo
attr_accessor :sudo_user
attr_accessor :tags attr_accessor :tags
attr_accessor :vault_password_file attr_accessor :vault_password_file
attr_accessor :verbose attr_accessor :verbose
attr_accessor :version
#
# Deprecated options
#
alias :sudo :become
def sudo=(value)
show_deprecation_info 'sudo', 'become'
@become = value
end
alias :sudo_user :become_user
def sudo_user=(value)
show_deprecation_info 'sudo_user', 'become_user'
@become_user = value
end
def initialize def initialize
@become = UNSET_VALUE
@become_user = UNSET_VALUE
@compatibility_mode = Ansible::COMPATIBILITY_MODE_AUTO
@config_file = UNSET_VALUE @config_file = UNSET_VALUE
@extra_vars = UNSET_VALUE @extra_vars = UNSET_VALUE
@galaxy_role_file = UNSET_VALUE @galaxy_role_file = UNSET_VALUE
@ -41,14 +62,16 @@ module VagrantPlugins
@raw_arguments = UNSET_VALUE @raw_arguments = UNSET_VALUE
@skip_tags = UNSET_VALUE @skip_tags = UNSET_VALUE
@start_at_task = UNSET_VALUE @start_at_task = UNSET_VALUE
@sudo = UNSET_VALUE
@sudo_user = UNSET_VALUE
@tags = UNSET_VALUE @tags = UNSET_VALUE
@vault_password_file = UNSET_VALUE @vault_password_file = UNSET_VALUE
@verbose = UNSET_VALUE @verbose = UNSET_VALUE
@version = UNSET_VALUE
end end
def finalize! def finalize!
@become = false if @become != true
@become_user = nil if @become_user == UNSET_VALUE
@compatibility_mode = nil unless Ansible::COMPATIBILITY_MODES.include?(@compatibility_mode)
@config_file = nil if @config_file == UNSET_VALUE @config_file = nil if @config_file == UNSET_VALUE
@extra_vars = nil if @extra_vars == UNSET_VALUE @extra_vars = nil if @extra_vars == UNSET_VALUE
@galaxy_role_file = nil if @galaxy_role_file == UNSET_VALUE @galaxy_role_file = nil if @galaxy_role_file == UNSET_VALUE
@ -63,11 +86,10 @@ module VagrantPlugins
@raw_arguments = nil if @raw_arguments == UNSET_VALUE @raw_arguments = nil if @raw_arguments == UNSET_VALUE
@skip_tags = nil if @skip_tags == UNSET_VALUE @skip_tags = nil if @skip_tags == UNSET_VALUE
@start_at_task = nil if @start_at_task == UNSET_VALUE @start_at_task = nil if @start_at_task == UNSET_VALUE
@sudo = false if @sudo != true
@sudo_user = nil if @sudo_user == UNSET_VALUE
@tags = nil if @tags == UNSET_VALUE @tags = nil if @tags == UNSET_VALUE
@vault_password_file = nil if @vault_password_file == UNSET_VALUE @vault_password_file = nil if @vault_password_file == UNSET_VALUE
@verbose = false if @verbose == UNSET_VALUE @verbose = false if @verbose == UNSET_VALUE
@version = "" if @version == UNSET_VALUE
end end
# Just like the normal configuration "validate" method except that # Just like the normal configuration "validate" method except that
@ -76,6 +98,12 @@ module VagrantPlugins
def validate(machine) def validate(machine)
@errors = _detected_errors @errors = _detected_errors
# Validate that a compatibility mode was provided
if !compatibility_mode
@errors << I18n.t("vagrant.provisioners.ansible.errors.no_compatibility_mode",
valid_modes: Ansible::COMPATIBILITY_MODES.map { |s| "'#{s}'" }.join(', '))
end
# Validate that a playbook path was provided # Validate that a playbook path was provided
if !playbook if !playbook
@errors << I18n.t("vagrant.provisioners.ansible.errors.no_playbook") @errors << I18n.t("vagrant.provisioners.ansible.errors.no_playbook")
@ -112,6 +140,14 @@ module VagrantPlugins
end end
end end
protected
def show_deprecation_info(deprecated_option, new_option)
puts "DEPRECATION: The '#{deprecated_option}' option for the Ansible provisioner is deprecated."
puts "Please use the '#{new_option}' option instead."
puts "The '#{deprecated_option}' option will be removed in a future release of Vagrant.\n\n"
end
end end
end end
end end

View File

@ -11,7 +11,6 @@ module VagrantPlugins
attr_accessor :install attr_accessor :install
attr_accessor :install_mode attr_accessor :install_mode
attr_accessor :pip_args attr_accessor :pip_args
attr_accessor :version
def initialize def initialize
super super
@ -21,7 +20,6 @@ module VagrantPlugins
@pip_args = UNSET_VALUE @pip_args = UNSET_VALUE
@provisioning_path = UNSET_VALUE @provisioning_path = UNSET_VALUE
@tmp_path = UNSET_VALUE @tmp_path = UNSET_VALUE
@version = UNSET_VALUE
end end
def finalize! def finalize!
@ -32,7 +30,6 @@ module VagrantPlugins
@pip_args = "" if @pip_args == UNSET_VALUE @pip_args = "" if @pip_args == UNSET_VALUE
@provisioning_path = "/vagrant" if provisioning_path == UNSET_VALUE @provisioning_path = "/vagrant" if provisioning_path == UNSET_VALUE
@tmp_path = "/tmp/vagrant-ansible" if tmp_path == UNSET_VALUE @tmp_path = "/tmp/vagrant-ansible" if tmp_path == UNSET_VALUE
@version = "" if @version == UNSET_VALUE
end end
def validate(machine) def validate(machine)

View File

@ -5,16 +5,25 @@ module VagrantPlugins
module Config module Config
class Host < Base class Host < Base
attr_accessor :ask_sudo_pass attr_accessor :ask_become_pass
attr_accessor :ask_vault_pass attr_accessor :ask_vault_pass
attr_accessor :force_remote_user attr_accessor :force_remote_user
attr_accessor :host_key_checking attr_accessor :host_key_checking
attr_accessor :raw_ssh_args attr_accessor :raw_ssh_args
#
# Deprecated options
#
alias :ask_sudo_pass :ask_become_pass
def ask_sudo_pass=(value)
show_deprecation_warning 'ask_sudo_pass', 'ask_become_pass'
@ask_become_pass = value
end
def initialize def initialize
super super
@ask_sudo_pass = false @ask_become_pass = false
@ask_vault_pass = false @ask_vault_pass = false
@force_remote_user = true @force_remote_user = true
@host_key_checking = false @host_key_checking = false
@ -24,7 +33,7 @@ module VagrantPlugins
def finalize! def finalize!
super super
@ask_sudo_pass = false if @ask_sudo_pass != true @ask_become_pass = false if @ask_become_pass != true
@ask_vault_pass = false if @ask_vault_pass != true @ask_vault_pass = false if @ask_vault_pass != true
@force_remote_user = true if @force_remote_user != false @force_remote_user = true if @force_remote_user != false
@host_key_checking = false if @host_key_checking != true @host_key_checking = false if @host_key_checking != true

View File

@ -0,0 +1,14 @@
module VagrantPlugins
module Ansible
COMPATIBILITY_MODE_AUTO = "auto".freeze
COMPATIBILITY_MODE_V1_8 = "1.8".freeze
COMPATIBILITY_MODE_V2_0 = "2.0".freeze
SAFE_COMPATIBILITY_MODE = COMPATIBILITY_MODE_V1_8
COMPATIBILITY_MODES = [
COMPATIBILITY_MODE_AUTO,
COMPATIBILITY_MODE_V1_8,
COMPATIBILITY_MODE_V2_0,
].freeze
end
end

View File

@ -11,21 +11,30 @@ module VagrantPlugins
error_key(:ansible_command_failed) error_key(:ansible_command_failed)
end end
class AnsibleNotFoundOnHost < AnsibleError class AnsibleCompatibilityModeConflict < AnsibleError
error_key(:ansible_not_found_on_host) error_key(:ansible_compatibility_mode_conflict)
end end
class AnsibleNotFoundOnGuest < AnsibleError class AnsibleNotFoundOnGuest < AnsibleError
error_key(:ansible_not_found_on_guest) error_key(:ansible_not_found_on_guest)
end end
class AnsibleNotFoundOnHost < AnsibleError
error_key(:ansible_not_found_on_host)
end
class AnsiblePipInstallIsNotSupported < AnsibleError class AnsiblePipInstallIsNotSupported < AnsibleError
error_key(:cannot_support_pip_install) error_key(:cannot_support_pip_install)
end end
class AnsibleVersionNotFoundOnGuest < AnsibleError class AnsibleProgrammingError < AnsibleError
error_key(:ansible_version_not_found_on_guest) error_key(:ansible_programming_error)
end end
class AnsibleVersionMismatch < AnsibleError
error_key(:ansible_version_mismatch)
end
end end
end end
end end

View File

@ -1,3 +1,4 @@
require_relative "../constants"
require_relative "../errors" require_relative "../errors"
require_relative "../helpers" require_relative "../helpers"
@ -14,15 +15,80 @@ module VagrantPlugins
RANGE_PATTERN = %r{(?:\[[a-z]:[a-z]\]|\[[0-9]+?:[0-9]+?\])}.freeze RANGE_PATTERN = %r{(?:\[[a-z]:[a-z]\]|\[[0-9]+?:[0-9]+?\])}.freeze
ANSIBLE_PARAMETER_NAMES = {
Ansible::COMPATIBILITY_MODE_V1_8 => {
ansible_host: "ansible_ssh_host",
ansible_password: "ansible_ssh_pass",
ansible_port: "ansible_ssh_port",
ansible_user: "ansible_ssh_user",
ask_become_pass: "ask-sudo-pass",
become: "sudo",
become_user: "sudo-user",
},
Ansible::COMPATIBILITY_MODE_V2_0 => {
ansible_host: "ansible_host",
ansible_password: "ansible_password",
ansible_port: "ansible_port",
ansible_user: "ansible_user",
ask_become_pass: "ask-become-pass",
become: "become",
become_user: "become-user",
}
}
protected protected
def initialize(machine, config) def initialize(machine, config)
super super
@control_machine = nil
@command_arguments = [] @command_arguments = []
@environment_variables = {} @environment_variables = {}
@inventory_machines = {} @inventory_machines = {}
@inventory_path = nil @inventory_path = nil
@gathered_version_stdout = nil
@gathered_version_major = nil
@gathered_version = nil
end
def set_and_check_compatibility_mode
begin
set_gathered_ansible_version(gather_ansible_version)
rescue StandardError => e
# Nothing to do here, as the fallback on safe compatibility_mode is done below
@logger.error("Error while gathering the ansible version: #{e.to_s}")
end
if @gathered_version_major
if config.compatibility_mode == Ansible::COMPATIBILITY_MODE_AUTO
detect_compatibility_mode
elsif @gathered_version_major.to_i < 2 && config.compatibility_mode == Ansible::COMPATIBILITY_MODE_V2_0
# A better version comparator will be needed
# when more compatibility modes come... but so far let's keep it simple!
raise Ansible::Errors::AnsibleCompatibilityModeConflict,
ansible_version: @gathered_version,
system: @control_machine,
compatibility_mode: config.compatibility_mode
end
end
if config.compatibility_mode == Ansible::COMPATIBILITY_MODE_AUTO
config.compatibility_mode = Ansible::SAFE_COMPATIBILITY_MODE
@machine.env.ui.warn(I18n.t("vagrant.provisioners.ansible.compatibility_mode_not_detected",
compatibility_mode: config.compatibility_mode,
gathered_version: @gathered_version_stdout) +
"\n")
end
unless Ansible::COMPATIBILITY_MODES.slice(1..-1).include?(config.compatibility_mode)
raise Ansible::Errors::AnsibleProgrammingError,
message: "The config.compatibility_mode must be correctly set at this stage!",
details: "config.compatibility_mode: '#{config.compatibility_mode}'"
end
@lexicon = ANSIBLE_PARAMETER_NAMES[config.compatibility_mode]
end end
def check_files_existence def check_files_existence
@ -70,7 +136,7 @@ module VagrantPlugins
if arg =~ /(--start-at-task|--limit)=(.+)/ if arg =~ /(--start-at-task|--limit)=(.+)/
shell_args << %Q(#{$1}="#{$2}") shell_args << %Q(#{$1}="#{$2}")
elsif arg =~ /(--extra-vars)=(.+)/ elsif arg =~ /(--extra-vars)=(.+)/
shell_args << %Q(%s="%s") % [$1, $2.gsub('\\', '\\\\\\').gsub('"', %Q(\\"))] shell_args << %Q(%s=%s) % [$1, $2.shellescape]
else else
shell_args << arg shell_args << arg
end end
@ -97,8 +163,8 @@ module VagrantPlugins
@command_arguments << "--inventory-file=#{inventory_path}" @command_arguments << "--inventory-file=#{inventory_path}"
@command_arguments << "--extra-vars=#{extra_vars_argument}" if config.extra_vars @command_arguments << "--extra-vars=#{extra_vars_argument}" if config.extra_vars
@command_arguments << "--sudo" if config.sudo @command_arguments << "--#{@lexicon[:become]}" if config.become
@command_arguments << "--sudo-user=#{config.sudo_user}" if config.sudo_user @command_arguments << "--#{@lexicon[:become_user]}=#{config.become_user}" if config.become_user
@command_arguments << "#{verbosity_argument}" if verbosity_is_enabled? @command_arguments << "#{verbosity_argument}" if verbosity_is_enabled?
@command_arguments << "--vault-password-file=#{config.vault_password_file}" if config.vault_password_file @command_arguments << "--vault-password-file=#{config.vault_password_file}" if config.vault_password_file
@command_arguments << "--tags=#{Helpers::as_list_argument(config.tags)}" if config.tags @command_arguments << "--tags=#{Helpers::as_list_argument(config.tags)}" if config.tags
@ -148,7 +214,13 @@ module VagrantPlugins
end end
s = nil s = nil
if vars.is_a?(Hash) if vars.is_a?(Hash)
s = vars.each.collect{ |k, v| "#{k}=#{v}" }.join(" ") s = vars.each.collect {
|k, v|
if v.is_a?(String) && v.include?(' ') && !v.match(/^('|")[^'"]+('|")$/)
v = %Q('#{v}')
end
"#{k}=#{v}"
}.join(" ")
elsif vars.is_a?(Array) elsif vars.is_a?(Array)
s = vars.join(" ") s = vars.join(" ")
elsif vars.is_a?(String) elsif vars.is_a?(String)
@ -228,7 +300,7 @@ module VagrantPlugins
end end
group_vars.each_pair do |gname, gmembers| group_vars.each_pair do |gname, gmembers|
if defined_groups.include?(gname.sub(/:vars$/, "")) if defined_groups.include?(gname.sub(/:vars$/, "")) || gname == "all:vars"
inventory_groups += "\n[#{gname}]\n" + gmembers.join("\n") + "\n" inventory_groups += "\n[#{gname}]\n" + gmembers.join("\n") + "\n"
end end
end end
@ -285,6 +357,44 @@ module VagrantPlugins
end end
end end
private
def detect_compatibility_mode
if !@gathered_version_major || config.compatibility_mode != Ansible::COMPATIBILITY_MODE_AUTO
raise Ansible::Errors::AnsibleProgrammingError,
message: "The detect_compatibility_mode() function shouldn't have been called!",
details: %Q(config.compatibility_mode: '#{config.compatibility_mode}'
gathered version major number: '#{@gathered_version_major}'
gathered version stdout version:
#{@gathered_version_stdout})
end
if @gathered_version_major.to_i <= 1
config.compatibility_mode = Ansible::COMPATIBILITY_MODE_V1_8
else
config.compatibility_mode = Ansible::COMPATIBILITY_MODE_V2_0
end
@machine.env.ui.warn(I18n.t("vagrant.provisioners.ansible.compatibility_mode_warning",
compatibility_mode: config.compatibility_mode,
ansible_version: @gathered_version) +
"\n")
end
def set_gathered_ansible_version(stdout_output)
@gathered_version_stdout = stdout_output
if !@gathered_version_stdout.empty?
first_line = @gathered_version_stdout.lines[0]
ansible_version_pattern = first_line.match(/(^ansible\s+)(.+)$/)
if ansible_version_pattern
_, @gathered_version, _ = ansible_version_pattern.captures
if @gathered_version
@gathered_version_major = @gathered_version.match(/^(\d)\..+$/).captures[0].to_i
end
end
end
end
end end
end end
end end

View File

@ -10,12 +10,14 @@ module VagrantPlugins
def initialize(machine, config) def initialize(machine, config)
super super
@control_machine = "guest"
@logger = Log4r::Logger.new("vagrant::provisioners::ansible_guest") @logger = Log4r::Logger.new("vagrant::provisioners::ansible_guest")
end end
def provision def provision
check_files_existence check_files_existence
check_and_install_ansible check_and_install_ansible
execute_ansible_galaxy_on_guest if config.galaxy_role_file execute_ansible_galaxy_on_guest if config.galaxy_role_file
execute_ansible_playbook_on_guest execute_ansible_playbook_on_guest
end end
@ -25,14 +27,14 @@ module VagrantPlugins
# #
# This handles verifying the Ansible installation, installing it if it was # This handles verifying the Ansible installation, installing it if it was
# requested, and so on. This method will raise exceptions if things are wrong. # requested, and so on. This method will raise exceptions if things are wrong.
# The compatibility mode checks are also performed here in order to fetch the
# Ansible version information only once.
# #
# Current limitations: # Current limitations:
# - The installation of a specific Ansible version is not supported. # - The installation of a specific Ansible version is only supported by
# Such feature is difficult to systematically provide via package repositories (apt, yum, ...). # the "pip" install_mode.
# Installing via pip python packaging or directly from github source would be appropriate, # - There is no absolute guarantee that the automated installation will replace
# but these approaches require more dependency burden. # a previous Ansible installation (although it works fine in many cases)
# - There is no guarantee that the automated installation will replace
# a previous Ansible installation.
# #
def check_and_install_ansible def check_and_install_ansible
@logger.info("Checking for Ansible installation...") @logger.info("Checking for Ansible installation...")
@ -52,21 +54,39 @@ module VagrantPlugins
@machine.guest.capability(:ansible_install, config.install_mode, config.version, config.pip_args) @machine.guest.capability(:ansible_install, config.install_mode, config.version, config.pip_args)
end end
# Check that Ansible Playbook command is available on the guest # This step will also fetch the Ansible version data into related instance variables
@machine.communicate.execute( set_and_check_compatibility_mode
"test -x \"$(command -v #{config.playbook_command})\"",
error_class: Ansible::Errors::AnsibleNotFoundOnGuest,
error_key: :ansible_not_found_on_guest
)
# Check if requested ansible version is available # Check if requested ansible version is available
if (!config.version.empty? && if (!config.version.empty? &&
config.version.to_s.to_sym != :latest && config.version.to_s.to_sym != :latest &&
!@machine.guest.capability(:ansible_installed, config.version)) config.version != @gathered_version)
raise Ansible::Errors::AnsibleVersionNotFoundOnGuest, required_version: config.version.to_s raise Ansible::Errors::AnsibleVersionMismatch,
system: @control_machine,
required_version: config.version,
current_version: @gathered_version
end end
end end
def gather_ansible_version
raw_output = ""
result = @machine.communicate.execute(
"ansible --version",
error_class: Ansible::Errors::AnsibleNotFoundOnGuest,
error_key: :ansible_not_found_on_guest) do |type, output|
if type == :stdout && output.lines[0]
raw_output = output.lines[0]
end
end
if result != 0
raw_output = ""
end
raw_output
end
def get_provisioning_working_directory def get_provisioning_working_directory
config.provisioning_path config.provisioning_path
end end
@ -159,7 +179,7 @@ module VagrantPlugins
error_key: :config_file_not_found, error_key: :config_file_not_found,
config_option: option_name, config_option: option_name,
path: remote_path, path: remote_path,
system: "guest" system: @control_machine
) )
end end

View File

@ -11,6 +11,7 @@ module VagrantPlugins
def initialize(machine, config) def initialize(machine, config)
super super
@control_machine = "host"
@logger = Log4r::Logger.new("vagrant::provisioners::ansible_host") @logger = Log4r::Logger.new("vagrant::provisioners::ansible_host")
end end
@ -18,8 +19,9 @@ module VagrantPlugins
# At this stage, the SSH access is guaranteed to be ready # At this stage, the SSH access is guaranteed to be ready
@ssh_info = @machine.ssh_info @ssh_info = @machine.ssh_info
check_files_existence
warn_for_unsupported_platform warn_for_unsupported_platform
check_files_existence
check_ansible_version_and_compatibility
execute_ansible_galaxy_from_host if config.galaxy_role_file execute_ansible_galaxy_from_host if config.galaxy_role_file
execute_ansible_playbook_from_host execute_ansible_playbook_from_host
@ -31,7 +33,24 @@ module VagrantPlugins
def warn_for_unsupported_platform def warn_for_unsupported_platform
if Vagrant::Util::Platform.windows? if Vagrant::Util::Platform.windows?
@machine.env.ui.warn(I18n.t("vagrant.provisioners.ansible.windows_not_supported_for_control_machine")) @machine.env.ui.warn(I18n.t("vagrant.provisioners.ansible.windows_not_supported_for_control_machine") + "\n")
end
end
def check_ansible_version_and_compatibility
# This step will also fetch the Ansible version data into related instance variables
set_and_check_compatibility_mode
# Skip this check when not required, nor possible
if !@gathered_version || config.version.empty? || config.version.to_s.to_sym == :latest
return
end
if config.version != @gathered_version
raise Ansible::Errors::AnsibleVersionMismatch,
system: @control_machine,
required_version: config.version,
current_version: @gathered_version
end end
end end
@ -49,15 +68,15 @@ module VagrantPlugins
if !config.force_remote_user if !config.force_remote_user
# Pass the vagrant ssh username as Ansible default remote user, because # Pass the vagrant ssh username as Ansible default remote user, because
# the ansible_ssh_user parameter won't be added to the auto-generated inventory. # the ansible_ssh_user/ansible_user parameter won't be added to the auto-generated inventory.
@command_arguments << "--user=#{@ssh_info[:username]}" @command_arguments << "--user=#{@ssh_info[:username]}"
elsif config.inventory_path elsif config.inventory_path
# Using an extra variable is the only way to ensure that the Ansible remote user # Using an extra variable is the only way to ensure that the Ansible remote user
# is overridden (as the ansible inventory is not under vagrant control) # is overridden (as the ansible inventory is not under vagrant control)
@command_arguments << "--extra-vars=ansible_ssh_user='#{@ssh_info[:username]}'" @command_arguments << "--extra-vars=#{@lexicon[:ansible_user]}='#{@ssh_info[:username]}'"
end end
@command_arguments << "--ask-sudo-pass" if config.ask_sudo_pass @command_arguments << "--#{@lexicon[:ask_become_pass]}" if config.ask_become_pass
@command_arguments << "--ask-vault-pass" if config.ask_vault_pass @command_arguments << "--ask-vault-pass" if config.ask_vault_pass
prepare_common_command_arguments prepare_common_command_arguments
@ -88,6 +107,30 @@ module VagrantPlugins
end end
end end
def gather_ansible_version
raw_output = ""
command = %w(ansible --version)
command << {
notify: [:stdout, :stderr]
}
begin
result = Vagrant::Util::Subprocess.execute(*command) do |type, output|
if type == :stdout && output.lines[0]
raw_output = output
end
end
if result.exit_code != 0
raw_output = ""
end
rescue Vagrant::Errors::CommandUnavailable
raise Ansible::Errors::AnsibleNotFoundOnHost
end
raw_output
end
def execute_ansible_galaxy_from_host def execute_ansible_galaxy_from_host
prepare_ansible_config_environment_variable prepare_ansible_config_environment_variable
@ -199,19 +242,19 @@ module VagrantPlugins
def get_inventory_ssh_machine(machine, ssh_info) def get_inventory_ssh_machine(machine, ssh_info)
forced_remote_user = "" forced_remote_user = ""
if config.force_remote_user if config.force_remote_user
forced_remote_user = "ansible_ssh_user='#{ssh_info[:username]}' " forced_remote_user = "#{@lexicon[:ansible_user]}='#{ssh_info[:username]}' "
end end
"#{machine.name} ansible_ssh_host=#{ssh_info[:host]} ansible_ssh_port=#{ssh_info[:port]} #{forced_remote_user}ansible_ssh_private_key_file='#{ssh_info[:private_key_path][0]}'\n" "#{machine.name} #{@lexicon[:ansible_host]}=#{ssh_info[:host]} #{@lexicon[:ansible_port]}=#{ssh_info[:port]} #{forced_remote_user}ansible_ssh_private_key_file='#{ssh_info[:private_key_path][0]}'\n"
end end
def get_inventory_winrm_machine(machine, winrm_net_info) def get_inventory_winrm_machine(machine, winrm_net_info)
forced_remote_user = "" forced_remote_user = ""
if config.force_remote_user if config.force_remote_user
forced_remote_user = "ansible_ssh_user='#{machine.config.winrm.username}' " forced_remote_user = "#{@lexicon[:ansible_user]}='#{machine.config.winrm.username}' "
end end
"#{machine.name} ansible_connection=winrm ansible_ssh_host=#{winrm_net_info[:host]} ansible_ssh_port=#{winrm_net_info[:port]} #{forced_remote_user}ansible_ssh_pass='#{machine.config.winrm.password}'\n" "#{machine.name} ansible_connection=winrm #{@lexicon[:ansible_host]}=#{winrm_net_info[:host]} #{@lexicon[:ansible_port]}=#{winrm_net_info[:port]} #{forced_remote_user}#{@lexicon[:ansible_password]}='#{machine.config.winrm.password}'\n"
end end
def ansible_ssh_args def ansible_ssh_args
@ -287,7 +330,7 @@ module VagrantPlugins
_key: :config_file_not_found, _key: :config_file_not_found,
config_option: option_name, config_option: option_name,
path: expanded_path, path: expanded_path,
system: "host" system: @control_machine
end end
end end

View File

@ -11,15 +11,28 @@ module VagrantPlugins
if File.directory?(source) if File.directory?(source)
# We need to make sure the actual destination folder # We need to make sure the actual destination folder
# also exists before uploading, otherwise # also exists before uploading, otherwise
# you will get nested folders. We also need to append # you will get nested folders
# a './' to the source folder so we copy the contents
# rather than the folder itself, in case a users destination
# folder differs from its source.
# #
# https://serverfault.com/questions/538368/make-scp-always-overwrite-or-create-directory # https://serverfault.com/questions/538368/make-scp-always-overwrite-or-create-directory
# https://unix.stackexchange.com/questions/292641/get-scp-path-behave-like-rsync-path/292732 # https://unix.stackexchange.com/questions/292641/get-scp-path-behave-like-rsync-path/292732
command = "mkdir -p \"%s\"" % destination command = "mkdir -p \"%s\"" % destination
source << "/." if !destination.end_with?(File::SEPARATOR) &&
!source.end_with?("#{File::SEPARATOR}.")
# We also need to append a '/.' to the source folder so we copy
# the contents rather than the folder itself, in case a users
# destination folder differs from its source
#
# If source is set as `source/` it will lose the trailing
# slash due to how `File.expand_path` works, so we don't need
# a conditional for that case.
if @machine.config.vm.communicator == :winrm
# windows needs an array of paths because of the
# winrm-fs function Vagrant is using to upload file/folder.
source = Dir["#{source}#{File::SEPARATOR}*"]
else
source << "#{File::SEPARATOR}."
end
end
else else
command = "mkdir -p \"%s\"" % File.dirname(destination) command = "mkdir -p \"%s\"" % File.dirname(destination)
end end

View File

@ -24,6 +24,8 @@ module VagrantPlugins
attr_accessor :log_level attr_accessor :log_level
attr_accessor :masterless attr_accessor :masterless
attr_accessor :minion_id attr_accessor :minion_id
attr_accessor :salt_call_args
attr_accessor :salt_args
## bootstrap options ## bootstrap options
attr_accessor :temp_config_dir attr_accessor :temp_config_dir
@ -68,6 +70,8 @@ module VagrantPlugins
@python_version = UNSET_VALUE @python_version = UNSET_VALUE
@run_service = UNSET_VALUE @run_service = UNSET_VALUE
@master_id = UNSET_VALUE @master_id = UNSET_VALUE
@salt_call_args = UNSET_VALUE
@salt_args = UNSET_VALUE
end end
def finalize! def finalize!
@ -94,6 +98,8 @@ module VagrantPlugins
@python_version = nil if @python_version == UNSET_VALUE @python_version = nil if @python_version == UNSET_VALUE
@run_service = nil if @run_service == UNSET_VALUE @run_service = nil if @run_service == UNSET_VALUE
@master_id = nil if @master_id == UNSET_VALUE @master_id = nil if @master_id == UNSET_VALUE
@salt_call_args = nil if @salt_call_args == UNSET_VALUE
@salt_args = nil if @salt_args == UNSET_VALUE
# NOTE: Optimistic defaults are set in the provisioner. UNSET_VALUEs # NOTE: Optimistic defaults are set in the provisioner. UNSET_VALUEs
# are converted there to allow proper detection of unset values. # are converted there to allow proper detection of unset values.
@ -149,6 +155,14 @@ module VagrantPlugins
errors << I18n.t("vagrant.provisioners.salt.must_accept_keys") errors << I18n.t("vagrant.provisioners.salt.must_accept_keys")
end end
if @salt_call_args && !@salt_call_args.is_a?(Array)
errors << I18n.t("vagrant.provisioners.salt.args_array")
end
if @salt_args && !@salt_args.is_a?(Array)
errors << I18n.t("vagrant.provisioners.salt.args_array")
end
return {"salt provisioner" => errors} return {"salt provisioner" => errors}
end end
end end

View File

@ -198,6 +198,16 @@ module VagrantPlugins
return options return options
end end
# Append additional arguments to the salt command
def get_salt_args
" " + Array(@config.salt_args).join(" ")
end
# Append additional arguments to the salt-call command
def get_call_args
" " + Array(@config.salt_call_args).join(" ")
end
# Copy master and minion configs to VM # Copy master and minion configs to VM
def upload_configs def upload_configs
if @config.minion_config if @config.minion_config
@ -368,7 +378,8 @@ module VagrantPlugins
unless @config.masterless? unless @config.masterless?
@machine.communicate.sudo("salt '*' saltutil.sync_all") @machine.communicate.sudo("salt '*' saltutil.sync_all")
end end
@machine.communicate.sudo("salt '*' state.highstate --verbose#{get_masterless}#{get_loglevel}#{get_colorize}#{get_pillar}", ssh_opts) do |type, data| options = "#{get_masterless}#{get_loglevel}#{get_colorize}#{get_pillar}#{get_salt_args}"
@machine.communicate.sudo("salt '*' state.highstate --verbose#{options}", ssh_opts) do |type, data|
if @config.verbose if @config.verbose
@machine.env.ui.info(data.rstrip) @machine.env.ui.info(data.rstrip)
end end
@ -380,7 +391,8 @@ module VagrantPlugins
@machine.communicate.execute("C:\\salt\\salt-call.bat saltutil.sync_all", opts) @machine.communicate.execute("C:\\salt\\salt-call.bat saltutil.sync_all", opts)
end end
# TODO: something equivalent to { error_key: :ssh_bad_exit_status_muted }? # TODO: something equivalent to { error_key: :ssh_bad_exit_status_muted }?
@machine.communicate.execute("C:\\salt\\salt-call.bat state.highstate --retcode-passthrough#{get_masterless}#{get_loglevel}#{get_colorize}#{get_pillar}", opts) do |type, data| options = "#{get_masterless}#{get_loglevel}#{get_colorize}#{get_pillar}#{get_call_args}"
@machine.communicate.execute("C:\\salt\\salt-call.bat state.highstate --retcode-passthrough#{options}", opts) do |type, data|
if @config.verbose if @config.verbose
@machine.env.ui.info(data.rstrip) @machine.env.ui.info(data.rstrip)
end end
@ -389,7 +401,8 @@ module VagrantPlugins
unless @config.masterless? unless @config.masterless?
@machine.communicate.sudo("salt-call saltutil.sync_all") @machine.communicate.sudo("salt-call saltutil.sync_all")
end end
@machine.communicate.sudo("salt-call state.highstate --retcode-passthrough#{get_masterless}#{get_loglevel}#{get_colorize}#{get_pillar}", ssh_opts) do |type, data| options = "#{get_masterless}#{get_loglevel}#{get_colorize}#{get_pillar}#{get_call_args}"
@machine.communicate.sudo("salt-call state.highstate --retcode-passthrough#{options}", ssh_opts) do |type, data|
if @config.verbose if @config.verbose
@machine.env.ui.info(data.rstrip) @machine.env.ui.info(data.rstrip)
end end

View File

@ -0,0 +1,7 @@
#VAGRANT-BEGIN
# The contents below are automatically generated by Vagrant. Do not modify.
TYPE=eth
NM_CONTROLLED=<%= options.fetch(:nm_controlled, "no") %>
BOOTPROTO=dhcp
ONBOOT=yes
#VAGRANT-END

View File

@ -0,0 +1,3 @@
#VAGRANT-BEGIN
<%= options[:ip] %>/<%= options[:netmask] %>
#VAGRANT-END

View File

@ -0,0 +1,5 @@
#VAGRANT-BEGIN
<% if options[:gateway] %>
default via <%= options[:gateway] %>
<% end %>
#VAGRANT-END

View File

@ -0,0 +1,7 @@
#VAGRANT-BEGIN
# The contents below are automatically generated by Vagrant. Do not modify.
TYPE=eth
NM_CONTROLLED=<%= options.fetch(:nm_controlled, "no") %>
BOOTPROTO=static
ONBOOT=yes
#VAGRANT-END

View File

@ -912,6 +912,9 @@ en:
command: %{command} command: %{command}
stdout: %{stdout} stdout: %{stdout}
stderr: %{stderr} stderr: %{stderr}
nfs_dupe_permissions: |-
You have attempted to export the same nfs host path at %{hostpath} with
different nfs permissions. Please pick one permission and reload your guest.
nfs_cant_read_exports: |- nfs_cant_read_exports: |-
Vagrant can't read your current NFS exports! The exports file should be Vagrant can't read your current NFS exports! The exports file should be
readable by any user. This is usually caused by invalid permissions readable by any user. This is usually caused by invalid permissions
@ -2338,14 +2341,19 @@ en:
ansible_command_failed: |- ansible_command_failed: |-
Ansible failed to complete successfully. Any error output should be Ansible failed to complete successfully. Any error output should be
visible above. Please fix these errors and try again. visible above. Please fix these errors and try again.
ansible_compatibility_mode_conflict: |-
The requested Ansible compatibility mode (%{compatibility_mode}) is in conflict with
the Ansible installation on your Vagrant %{system} system (currently: %{ansible_version}).
See https://docs.vagrantup.com/v2/provisioning/ansible_common.html#compatibility_mode
for more information.
ansible_not_found_on_guest: |- ansible_not_found_on_guest: |-
The Ansible software could not be found! Please verify The Ansible software could not be found! Please verify
that Ansible is correctly installed on your guest system. that Ansible is correctly installed on your guest system.
If you haven't installed Ansible yet, please install Ansible If you haven't installed Ansible yet, please install Ansible
on your Vagrant basebox, or enable the automated setup with the on your Vagrant basebox, or enable the automated setup with the
`install` option of this provisioner. Please check ansible_local provisioner `install` option. Please check
https://docs.vagrantup.com/v2/provisioning/ansible_local.html https://docs.vagrantup.com/v2/provisioning/ansible_local.html#install
for more information. for more information.
ansible_not_found_on_host: |- ansible_not_found_on_host: |-
The Ansible software could not be found! Please verify The Ansible software could not be found! Please verify
@ -2355,6 +2363,18 @@ en:
on your host system. Vagrant can't do this for you in a safe and on your host system. Vagrant can't do this for you in a safe and
automated way. automated way.
Please check https://docs.ansible.com for more information. Please check https://docs.ansible.com for more information.
ansible_programming_error: |-
Ansible Provisioner Programming Error:
%{message}
Internal Details:
%{details}
Sorry, but this Vagrant error should never occur.
Please check https://github.com/mitchellh/vagrant/issues for any
existing bug report. If needed, please create a new issue. Thank you!
cannot_support_pip_install: |- cannot_support_pip_install: |-
Unfortunately Vagrant does not support yet installing Ansible Unfortunately Vagrant does not support yet installing Ansible
from pip for the guest OS running in the machine. from pip for the guest OS running in the machine.
@ -2364,16 +2384,18 @@ en:
to contribute back support. Thank you! to contribute back support. Thank you!
https://github.com/mitchellh/vagrant https://github.com/mitchellh/vagrant
ansible_version_not_found_on_guest: |- ansible_version_mismatch: |-
The requested Ansible version (%{required_version}) was not found on the guest. The requested Ansible version (%{required_version}) was not found on the %{system}.
Please check the ansible installation on your guest system, Please check the Ansible installation on your Vagrant %{system} system (currently: %{current_version}),
or adapt the `version` option of this provisioner in your Vagrantfile. or adapt the provisioner `version` option in your Vagrantfile.
See https://docs.vagrantup.com/v2/provisioning/ansible_local.html See https://docs.vagrantup.com/v2/provisioning/ansible_common.html#version
for more information. for more information.
config_file_not_found: |- config_file_not_found: |-
`%{config_option}` does not exist on the %{system}: %{path} `%{config_option}` does not exist on the %{system}: %{path}
extra_vars_invalid: |- extra_vars_invalid: |-
`extra_vars` must be a hash or a path to an existing file. Received: %{value} (as %{type}) `extra_vars` must be a hash or a path to an existing file. Received: %{value} (as %{type})
no_compatibility_mode: |-
`compatibility_mode` must be a valid mode (possible values: %{valid_modes}).
no_playbook: |- no_playbook: |-
`playbook` file path must be set. `playbook` file path must be set.
raw_arguments_invalid: |- raw_arguments_invalid: |-
@ -2387,6 +2409,20 @@ en:
windows_not_supported_for_control_machine: |- windows_not_supported_for_control_machine: |-
Windows is not officially supported for the Ansible Control Machine. Windows is not officially supported for the Ansible Control Machine.
Please check https://docs.ansible.com/intro_installation.html#control-machine-requirements Please check https://docs.ansible.com/intro_installation.html#control-machine-requirements
compatibility_mode_not_detected: |-
Vagrant gathered an unknown Ansible version:
%{gathered_version}
and falls back on the compatibility mode '%{compatibility_mode}'.
Alternatively, the compatibility mode can be specified in your Vagrantfile:
https://www.vagrantup.com/docs/provisioning/ansible_common.html#compatibility_mode
compatibility_mode_warning: |-
Vagrant has automatically selected the compatibility mode '%{compatibility_mode}'
according to the Ansible version installed (%{ansible_version}).
Alternatively, the compatibility mode can be specified in your Vagrantfile:
https://www.vagrantup.com/docs/provisioning/ansible_common.html#compatibility_mode
docker: docker:
wrong_provisioner: |- wrong_provisioner: |-
@ -2406,6 +2442,8 @@ 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!
args_array: |-
You must set this value as an array.
pushes: pushes:
file: file:

View File

@ -7,7 +7,12 @@ describe VagrantPlugins::LoginCommand::Client do
let(:env) { isolated_environment.create_vagrant_env } let(:env) { isolated_environment.create_vagrant_env }
subject { described_class.new(env) } subject(:client) { described_class.new(env) }
before(:all) do
I18n.load_path << Vagrant.source_root.join("plugins/commands/login/locales/en.yml")
I18n.reload!
end
before do before do
stub_env("ATLAS_TOKEN" => nil) stub_env("ATLAS_TOKEN" => nil)
@ -38,7 +43,7 @@ describe VagrantPlugins::LoginCommand::Client do
expect(subject.logged_in?).to be(true) expect(subject.logged_in?).to be(true)
end end
it "returns false if the endpoint returns a non-200" do it "raises an error if the endpoint returns a non-200" do
stub_request(:get, url) stub_request(:get, url)
.with(headers: headers) .with(headers: headers)
.to_return(body: JSON.pretty_generate("bad" => true), status: 401) .to_return(body: JSON.pretty_generate("bad" => true), status: 401)
@ -55,47 +60,159 @@ describe VagrantPlugins::LoginCommand::Client do
end end
describe "#login" do describe "#login" do
it "returns the access token after successful login" do let(:request) {
request = { {
"user" => { user: {
"login" => "foo", login: login,
"password" => "bar", password: password,
}, },
"token" => { token: {
"description" => "Token description" description: description,
},
two_factor: {
code: nil
} }
} }
}
response = { let(:login) { "foo" }
"token" => "baz", let(:password) { "bar" }
} let(:description) { "Token description" }
headers = { let(:headers) {
{
"Accept" => "application/json", "Accept" => "application/json",
"Content-Type" => "application/json", "Content-Type" => "application/json",
} }
}
let(:response) {
{
token: "baz"
}
}
it "returns the access token after successful login" do
stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate").
with(body: JSON.dump(request), headers: headers). with(body: JSON.dump(request), headers: headers).
to_return(status: 200, body: JSON.dump(response)) to_return(status: 200, body: JSON.dump(response))
expect(subject.login("foo", "bar", description: "Token description")) client.username_or_email = login
.to eq("baz") client.password = password
expect(client.login(description: "Token description")).to eq("baz")
end end
it "returns nil on bad login" do context "when 2fa is required" do
stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). let(:response) {
to_return(status: 401, body: "") {
two_factor: {
default_delivery_method: default_delivery_method,
delivery_methods: delivery_methods
}
}
}
let(:default_delivery_method) { "app" }
let(:delivery_methods) { ["app"] }
expect(subject.login("foo", "bar")).to be(false) before do
stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate").
to_return(status: 406, body: JSON.dump(response))
end
it "raises a two-factor required error" do
expect {
client.login
}.to raise_error(VagrantPlugins::LoginCommand::Errors::TwoFactorRequired)
end
context "when the default delivery method is not app" do
let(:default_delivery_method) { "sms" }
let(:delivery_methods) { ["app", "sms"] }
it "requests a code and then raises a two-factor required error" do
expect(client)
.to receive(:request_code)
.with(default_delivery_method)
expect {
client.login
}.to raise_error(VagrantPlugins::LoginCommand::Errors::TwoFactorRequired)
end
end
end end
it "raises an exception if it can't reach the sever" do context "on bad login" do
stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). before do
to_raise(SocketError) stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate").
to_return(status: 401, body: "")
end
expect { subject.login("foo", "bar") }. it "raises an error" do
to raise_error(VagrantPlugins::LoginCommand::Errors::ServerUnreachable) expect {
client.login
}.to raise_error(VagrantPlugins::LoginCommand::Errors::Unauthorized)
end
end
context "if it can't reach the server" do
before do
stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate").
to_raise(SocketError)
end
it "raises an exception" do
expect {
subject.login
}.to raise_error(VagrantPlugins::LoginCommand::Errors::ServerUnreachable)
end
end
end
describe "#request_code" do
let(:request) {
{
user: {
login: login,
password: password,
},
two_factor: {
delivery_method: delivery_method
}
}
}
let(:login) { "foo" }
let(:password) { "bar" }
let(:delivery_method) { "sms" }
let(:headers) {
{
"Accept" => "application/json",
"Content-Type" => "application/json"
}
}
let(:response) {
{
two_factor: {
obfuscated_destination: "SMS number ending in 1234"
}
}
}
it "displays that the code was sent" do
expect(env.ui)
.to receive(:success)
.with("2FA code sent to SMS number ending in 1234.")
stub_request(:post, "#{Vagrant.server_url}/api/v1/two-factor/request-code").
with(body: JSON.dump(request), headers: headers).
to_return(status: 201, body: JSON.dump(response))
client.username_or_email = login
client.password = password
client.request_code delivery_method
end end
end end

View File

@ -31,6 +31,35 @@ describe VagrantPlugins::CommunicatorWinRM::WinRMShell do
end end
end end
describe "#upload" do
let(:fm) { double("file_manager") }
it "should call file_manager.upload for each passed in path" do
from = ["/path", "/path/folder", "/path/folder/file.py"]
to = "/destination"
size = 80
allow(WinRM::FS::FileManager).to receive(:new).with(connection)
.and_return(fm)
allow(fm).to receive(:upload).and_return(size)
expect(fm).to receive(:upload).exactly(from.size).times
expect(subject.upload(from, to)).to eq(size*from.size)
end
it "should call file_manager.upload once for a single path" do
from = "/path/folder/file.py"
to = "/destination"
size = 80
allow(WinRM::FS::FileManager).to receive(:new).with(connection)
.and_return(fm)
allow(fm).to receive(:upload).and_return(size)
expect(fm).to receive(:upload).exactly(1).times
expect(subject.upload(from, to)).to eq(size)
end
end
describe ".powershell" do describe ".powershell" do
it "should call winrm powershell" do it "should call winrm powershell" do
expect(shell).to receive(:run).with("dir").and_return(output) expect(shell).to receive(:run).with("dir").and_return(output)
@ -66,7 +95,7 @@ describe VagrantPlugins::CommunicatorWinRM::WinRMShell do
end end
end end
describe ".cmd" do describe ".cmd" do
it "should call winrm cmd" do it "should call winrm cmd" do
expect(connection).to receive(:shell).with(:cmd, { }) expect(connection).to receive(:shell).with(:cmd, { })
expect(shell).to receive(:run).with("dir").and_return(output) expect(shell).to receive(:run).with("dir").and_return(output)

View File

@ -0,0 +1,42 @@
require_relative "../../../../base"
describe "VagrantPlugins::GuestALT::Cap::ChangeHostName" do
let(:caps) do
VagrantPlugins::GuestALT::Plugin
.components
.guest_capabilities[:alt]
end
let(:machine) { double("machine") }
let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }
before do
allow(machine).to receive(:communicate).and_return(comm)
end
after do
comm.verify_expectations!
end
describe ".change_host_name" do
let(:cap) { caps.get(:change_host_name) }
let(:name) { "banana-rama.example.com" }
it "sets the hostname" do
comm.stub_command("hostname -f | grep '^#{name}$'", exit_code: 1)
cap.change_host_name(machine, name)
expect(comm.received_commands[1]).to match(/\/etc\/sysconfig\/network/)
expect(comm.received_commands[1]).to match(/hostnamectl set-hostname --static '#{name}'/)
expect(comm.received_commands[1]).to match(/hostnamectl set-hostname --transient '#{name}'/)
expect(comm.received_commands[1]).to match(/service network restart/)
end
it "does not change the hostname if already set" do
comm.stub_command("hostname -f | grep '^#{name}$'", exit_code: 0)
cap.change_host_name(machine, name)
expect(comm.received_commands.size).to eq(1)
end
end
end

View File

@ -0,0 +1,213 @@
require_relative "../../../../base"
describe "VagrantPlugins::GuestALT::Cap::ConfigureNetworks" do
let(:caps) do
VagrantPlugins::GuestALT::Plugin
.components
.guest_capabilities[:alt]
end
let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }
let(:config) { double("config", vm: vm) }
let(:guest) { double("guest") }
let(:machine) { double("machine", guest: guest, config: config) }
let(:networks){ [[{}], [{}]] }
let(:vm){ double("vm", networks: networks) }
before do
allow(machine).to receive(:communicate).and_return(comm)
end
after do
comm.verify_expectations!
end
describe ".configure_networks" do
let(:cap) { caps.get(:configure_networks) }
before do
allow(guest).to receive(:capability)
.with(:flavor)
.and_return(:alt)
allow(guest).to receive(:capability)
.with(:network_scripts_dir)
.and_return("/etc/net")
allow(guest).to receive(:capability)
.with(:network_interfaces)
.and_return(["eth1", "eth2"])
end
let(:network_1) do
{
interface: 0,
type: "dhcp",
}
end
let(:network_2) do
{
interface: 1,
type: "static",
ip: "33.33.33.10",
netmask: "255.255.0.0",
gateway: "33.33.0.1",
}
end
context "with NetworkManager installed" do
before do
allow(cap).to receive(:nmcli?).and_return true
end
context "with devices managed by NetworkManager" do
before do
allow(cap).to receive(:nm_controlled?).and_return true
end
context "with nm_controlled option omitted" do
it "downs networks via nmcli, creates ifaces and restart NetworksManager" do
cap.configure_networks(machine, [network_1, network_2])
expect(comm.received_commands[0]).to match(/nmcli.*disconnect/)
expect(comm.received_commands[0]).to match(/mkdir.*\/etc\/net\/ifaces/)
expect(comm.received_commands[0]).to match(/NetworkManager/)
expect(comm.received_commands[0]).to_not match(/ifdown|ifup/)
end
end
context "with nm_controlled option set to true" do
let(:networks){ [[{nm_controlled: true}], [{nm_controlled: true}]] }
it "downs networks via nmcli, creates ifaces and restart NetworksManager" do
cap.configure_networks(machine, [network_1, network_2])
expect(comm.received_commands[0]).to match(/nmcli.*disconnect/)
expect(comm.received_commands[0]).to match(/mkdir.*\/etc\/net\/ifaces/)
expect(comm.received_commands[0]).to match(/NetworkManager/)
expect(comm.received_commands[0]).to_not match(/(ifdown|ifup)/)
end
end
context "with nm_controlled option set to false" do
let(:networks){ [[{nm_controlled: false}], [{nm_controlled: false}]] }
it "downs networks manually, creates ifaces, starts networks manually and restart NetworksManager" do
cap.configure_networks(machine, [network_1, network_2])
expect(comm.received_commands[0]).to match(/ifdown/)
expect(comm.received_commands[0]).to match(/mkdir.*\/etc\/net\/ifaces/)
expect(comm.received_commands[0]).to match(/ifup/)
expect(comm.received_commands[0]).to match(/NetworkManager/)
expect(comm.received_commands[0]).to_not match(/nmcli/)
end
end
context "with nm_controlled option set to false on first device" do
let(:networks){ [[{nm_controlled: false}], [{nm_controlled: true}]] }
it "downs networks, creates ifaces and starts the networks with one managed manually and one NetworkManager controlled" do
cap.configure_networks(machine, [network_1, network_2])
expect(comm.received_commands[0]).to match(/ifdown/)
expect(comm.received_commands[0]).to match(/nmcli.*disconnect/)
expect(comm.received_commands[0]).to match(/mkdir.*\/etc\/net\/ifaces/)
expect(comm.received_commands[0]).to match(/ifup/)
expect(comm.received_commands[0]).to match(/NetworkManager/)
end
end
end
context "with devices not managed by NetworkManager" do
before do
allow(cap).to receive(:nm_controlled?).and_return false
end
context "with nm_controlled option omitted" do
it "creates and starts the networks manually" do
cap.configure_networks(machine, [network_1, network_2])
expect(comm.received_commands[0]).to match(/ifdown/)
expect(comm.received_commands[0]).to match(/mkdir.*\/etc\/net\/ifaces/)
expect(comm.received_commands[0]).to match(/ifup/)
expect(comm.received_commands[0]).to match(/NetworkManager/)
expect(comm.received_commands[0]).to_not match(/nmcli/)
end
end
context "with nm_controlled option set to true" do
let(:networks){ [[{nm_controlled: true}], [{nm_controlled: true}]] }
it "creates and starts the networks via nmcli" do
cap.configure_networks(machine, [network_1, network_2])
expect(comm.received_commands[0]).to match(/ifdown/)
expect(comm.received_commands[0]).to match(/mkdir.*\/etc\/net\/ifaces/)
expect(comm.received_commands[0]).to match(/NetworkManager/)
expect(comm.received_commands[0]).to_not match(/ifup/)
end
end
context "with nm_controlled option set to false" do
let(:networks){ [[{nm_controlled: false}], [{nm_controlled: false}]] }
it "creates and starts the networks via ifup " do
cap.configure_networks(machine, [network_1, network_2])
expect(comm.received_commands[0]).to match(/ifdown/)
expect(comm.received_commands[0]).to match(/mkdir.*\/etc\/net\/ifaces/)
expect(comm.received_commands[0]).to match(/ifup/)
expect(comm.received_commands[0]).to match(/NetworkManager/)
expect(comm.received_commands[0]).to_not match(/nmcli/)
end
end
context "with nm_controlled option set to false on first device" do
let(:networks){ [[{nm_controlled: false}], [{nm_controlled: true}]] }
it "creates and starts the networks with one managed manually and one NetworkManager controlled" do
cap.configure_networks(machine, [network_1, network_2])
expect(comm.received_commands[0]).to match(/ifdown/)
expect(comm.received_commands[0]).to match(/mkdir.*\/etc\/net\/ifaces/)
expect(comm.received_commands[0]).to match(/ifup/)
expect(comm.received_commands[0]).to match(/NetworkManager/)
expect(comm.received_commands[0]).to_not match(/nmcli.*disconnect/)
end
end
end
end
context "without NetworkManager installed" do
before do
allow(cap).to receive(:nmcli?).and_return false
end
context "with nm_controlled option omitted" do
it "creates and starts the networks manually" do
cap.configure_networks(machine, [network_1, network_2])
expect(comm.received_commands[0]).to match(/ifdown/)
expect(comm.received_commands[0]).to match(/mkdir.*\/etc\/net\/ifaces/)
expect(comm.received_commands[0]).to match(/ifup/)
expect(comm.received_commands[0]).to_not match(/nmcli/)
expect(comm.received_commands[0]).to_not match(/NetworkManager/)
end
end
context "with nm_controlled option omitted" do
let(:networks){ [[{nm_controlled: false}], [{nm_controlled: false}]] }
it "creates and starts the networks manually" do
cap.configure_networks(machine, [network_1, network_2])
expect(comm.received_commands[0]).to match(/ifdown/)
expect(comm.received_commands[0]).to match(/mkdir.*\/etc\/net\/ifaces/)
expect(comm.received_commands[0]).to match(/ifup/)
expect(comm.received_commands[0]).to_not match(/nmcli/)
expect(comm.received_commands[0]).to_not match(/NetworkManager/)
end
end
context "with nm_controlled option set" do
let(:networks){ [[{nm_controlled: false}], [{nm_controlled: true}]] }
it "raises an error" do
expect{ cap.configure_networks(machine, [network_1, network_2]) }.to raise_error(Vagrant::Errors::NetworkManagerNotInstalled)
end
end
end
end
end

View File

@ -0,0 +1,72 @@
require_relative "../../../../base"
describe "VagrantPlugins::GuestALT::Cap::Flavor" do
let(:caps) do
VagrantPlugins::GuestALT::Plugin
.components
.guest_capabilities[:alt]
end
let(:machine) { double("machine") }
let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }
before do
allow(machine).to receive(:communicate).and_return(comm)
end
after do
comm.verify_expectations!
end
describe ".flavor" do
let(:cap) { caps.get(:flavor) }
context "without /etc/os-release file" do
{
"ALT 8.1 Server" => :alt_8,
"ALT Education 8.1" => :alt_8,
"ALT Workstation 8.1" => :alt_8,
"ALT Workstation K 8.1 (Centaurea Ruthenica)" => :alt_8,
"ALT Linux p8 (Hypericum)" => :alt_8,
"ALT Sisyphus (unstable) (sisyphus)" => :alt,
"ALT Linux Sisyphus (unstable)" => :alt,
"ALT Linux 6.0.1 Spt (separator)" => :alt,
"ALT Linux 7.0.5 School Master" => :alt,
"ALT starter kit (Hypericum)" => :alt,
"ALT" => :alt,
"Simply" => :alt,
}.each do |str, expected|
it "returns #{expected} for #{str} in /etc/altlinux-release" do
comm.stub_command("test -f /etc/os-release", exit_code: 1)
comm.stub_command("cat /etc/altlinux-release", stdout: str)
expect(cap.flavor(machine)).to be(expected)
end
end
end
context "with /etc/os-release file" do
{
[ "NAME=\"Sisyphus\"", "VERSION_ID=20161130" ] => :alt,
[ "NAME=\"ALT Education\"", "VERSION_ID=8.1" ] => :alt_8,
[ "NAME=\"ALT Server\"", "VERSION_ID=8.1" ] => :alt_8,
[ "NAME=\"ALT SPServer\"", "VERSION_ID=8.0" ] => :alt_8,
[ "NAME=\"starter kit\"", "VERSION_ID=p8" ] => :alt_8,
[ "NAME=\"ALT Linux\"", "VERSION_ID=8.0.0" ] => :alt_8,
[ "NAME=\"Simply Linux\"", "VERSION_ID=7.95.0" ] => :alt_8,
[ "NAME=\"ALT Linux\"", "VERSION_ID=7.0.5" ] => :alt_7,
[ "NAME=\"School Junior\"", "VERSION_ID=7.0.5" ] => :alt_7,
}.each do |strs, expected|
it "returns #{expected} for #{strs[0]} and #{strs[1]} in /etc/os-release" do
comm.stub_command("test -f /etc/os-release", exit_code: 0)
comm.stub_command("grep NAME /etc/os-release", stdout: strs[0])
comm.stub_command("grep VERSION_ID /etc/os-release", stdout: strs[1])
expect(cap.flavor(machine)).to be(expected)
end
end
end
end
end

View File

@ -0,0 +1,21 @@
require_relative "../../../../base"
describe "VagrantPlugins::GuestALT::Cap::NetworkScriptsDir" do
let(:caps) do
VagrantPlugins::GuestALT::Plugin
.components
.guest_capabilities[:alt]
end
let(:machine) { double("machine") }
describe ".network_scripts_dir" do
let(:cap) { caps.get(:network_scripts_dir) }
let(:name) { "banana-rama.example.com" }
it "is /etc/net" do
expect(cap.network_scripts_dir(machine)).to eq("/etc/net")
end
end
end

View File

@ -0,0 +1,29 @@
require_relative "../../../../base"
describe "VagrantPlugins::GuestALT::Cap:RSync" do
let(:caps) do
VagrantPlugins::GuestALT::Plugin
.components
.guest_capabilities[:alt]
end
let(:machine) { double("machine") }
let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }
before do
allow(machine).to receive(:communicate).and_return(comm)
end
after do
comm.verify_expectations!
end
describe ".rsync_install" do
let(:cap) { caps.get(:rsync_install) }
it "installs rsync" do
cap.rsync_install(machine)
expect(comm.received_commands[0]).to match(/install rsync/)
end
end
end

View File

@ -33,6 +33,44 @@ describe VagrantPlugins::HostLinux::Cap::NFS do
@tmp_exports = nil @tmp_exports = nil
end end
describe ".nfs_check_command" do
let(:cap){ caps.get(:nfs_check_command) }
context "without systemd" do
before{ expect(Vagrant::Util::Platform).to receive(:systemd?).and_return(false) }
it "should use init.d script" do
expect(cap.nfs_check_command(env)).to include("init.d")
end
end
context "with systemd" do
before{ expect(Vagrant::Util::Platform).to receive(:systemd?).and_return(true) }
it "should use systemctl" do
expect(cap.nfs_check_command(env)).to include("systemctl")
end
end
end
describe ".nfs_start_command" do
let(:cap){ caps.get(:nfs_start_command) }
context "without systemd" do
before{ expect(Vagrant::Util::Platform).to receive(:systemd?).and_return(false) }
it "should use init.d script" do
expect(cap.nfs_start_command(env)).to include("init.d")
end
end
context "with systemd" do
before{ expect(Vagrant::Util::Platform).to receive(:systemd?).and_return(true) }
it "should use systemctl" do
expect(cap.nfs_start_command(env)).to include("systemctl")
end
end
end
describe ".nfs_export" do describe ".nfs_export" do
let(:cap){ caps.get(:nfs_export) } let(:cap){ caps.get(:nfs_export) }
@ -43,8 +81,9 @@ describe VagrantPlugins::HostLinux::Cap::NFS do
allow(host).to receive(:capability).with(:nfs_check_command).and_return("/bin/true") allow(host).to receive(:capability).with(:nfs_check_command).and_return("/bin/true")
allow(host).to receive(:capability).with(:nfs_start_command).and_return("/bin/true") allow(host).to receive(:capability).with(:nfs_start_command).and_return("/bin/true")
allow(ui).to receive(:info) allow(ui).to receive(:info)
allow(cap).to receive(:system).with("sudo /bin/true").and_return(true) allow(Vagrant::Util::Subprocess).to receive(:execute).and_call_original
allow(cap).to receive(:system).with("/bin/true").and_return(true) allow(Vagrant::Util::Subprocess).to receive(:execute).with("sudo", "/bin/true").and_return(double(:result, exit_code: 0))
allow(Vagrant::Util::Subprocess).to receive(:execute).with("/bin/true").and_return(double(:result, exit_code: 0))
end end
it "should export new entries" do it "should export new entries" do
@ -79,6 +118,42 @@ EOH
expect(exports_content).to include("/tmp") expect(exports_content).to include("/tmp")
expect(exports_content).not_to include("/var") expect(exports_content).not_to include("/var")
end end
it "throws an exception with at least 2 different nfs options" do
folders = {"/vagrant"=>
{:hostpath=>"/home/vagrant",
:linux__nfs_options=>["rw","all_squash"]},
"/var/www/project"=>
{:hostpath=>"/home/vagrant",
:linux__nfs_options=>["rw","sync"]}}
expect { cap.nfs_export(env, ui, SecureRandom.uuid, ["127.0.0.1"], folders) }.
to raise_error Vagrant::Errors::NFSDupePerms
end
it "writes only 1 hostpath for multiple exports" do
folders = {"/vagrant"=>
{:hostpath=>"/home/vagrant",
:linux__nfs_options=>["rw","all_squash"]},
"/var/www/otherproject"=>
{:hostpath=>"/newhome/otherproject",
:linux__nfs_options=>["rw","all_squash"]},
"/var/www/project"=>
{:hostpath=>"/home/vagrant",
:linux__nfs_options=>["rw","all_squash"]}}
valid_id = SecureRandom.uuid
content =<<-EOH
\n# VAGRANT-BEGIN: #{Process.uid} #{valid_id}
"/home/vagrant" 127.0.0.1(rw,all_squash,anonuid=,anongid=,fsid=)
"/newhome/otherproject" 127.0.0.1(rw,all_squash,anonuid=,anongid=,fsid=)
# VAGRANT-END: #{Process.uid} #{valid_id}
EOH
cap.nfs_export(env, ui, valid_id, ["127.0.0.1"], folders)
exports_content = File.read(exports_path)
expect(exports_content).to eq(content)
end
end end
describe ".nfs_prune" do describe ".nfs_prune" do

View File

@ -16,7 +16,11 @@ describe VagrantPlugins::Ansible::Config::Guest do
let(:existing_file) { "this/path/is/a/stub" } let(:existing_file) { "this/path/is/a/stub" }
it "supports a list of options" do it "supports a list of options" do
supported_options = %w( config_file supported_options = %w(
become
become_user
compatibility_mode
config_file
extra_vars extra_vars
galaxy_command galaxy_command
galaxy_role_file galaxy_role_file
@ -40,7 +44,8 @@ describe VagrantPlugins::Ansible::Config::Guest do
tmp_path tmp_path
vault_password_file vault_password_file
verbose verbose
version ) version
)
expect(get_provisioner_option_names(described_class)).to eql(supported_options) expect(get_provisioner_option_names(described_class)).to eql(supported_options)
end end
@ -55,7 +60,6 @@ describe VagrantPlugins::Ansible::Config::Guest do
expect(subject.install_mode).to eql(:default) expect(subject.install_mode).to eql(:default)
expect(subject.provisioning_path).to eql("/vagrant") expect(subject.provisioning_path).to eql("/vagrant")
expect(subject.tmp_path).to eql("/tmp/vagrant-ansible") expect(subject.tmp_path).to eql("/tmp/vagrant-ansible")
expect(subject.version).to be_empty
end end
end end

View File

@ -13,8 +13,13 @@ describe VagrantPlugins::Ansible::Config::Host, :skip_windows => true do
let(:existing_file) { File.expand_path(__FILE__) } let(:existing_file) { File.expand_path(__FILE__) }
it "supports a list of options" do it "supports a list of options" do
supported_options = %w( ask_sudo_pass supported_options = %w(
ask_become_pass
ask_sudo_pass
ask_vault_pass ask_vault_pass
become
become_user
compatibility_mode
config_file config_file
extra_vars extra_vars
force_remote_user force_remote_user
@ -36,7 +41,9 @@ describe VagrantPlugins::Ansible::Config::Host, :skip_windows => true do
sudo_user sudo_user
tags tags
vault_password_file vault_password_file
verbose ) verbose
version
)
expect(get_provisioner_option_names(described_class)).to eql(supported_options) expect(get_provisioner_option_names(described_class)).to eql(supported_options)
end end
@ -47,7 +54,8 @@ describe VagrantPlugins::Ansible::Config::Host, :skip_windows => true do
it "assigns default values to unset host-specific options" do it "assigns default values to unset host-specific options" do
subject.finalize! subject.finalize!
expect(subject.ask_sudo_pass).to be(false) expect(subject.ask_become_pass).to be(false)
expect(subject.ask_sudo_pass).to be(false) # deprecated
expect(subject.ask_vault_pass).to be(false) expect(subject.ask_vault_pass).to be(false)
expect(subject.force_remote_user).to be(true) expect(subject.force_remote_user).to be(true)
expect(subject.host_key_checking).to be(false) expect(subject.host_key_checking).to be(false)
@ -61,7 +69,14 @@ describe VagrantPlugins::Ansible::Config::Host, :skip_windows => true do
describe "host_key_checking option" do describe "host_key_checking option" do
it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :host_key_checking, false it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :host_key_checking, false
end end
describe "ask_become_pass option" do
it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :ask_become_pass, false
end
describe "ask_sudo_pass option" do describe "ask_sudo_pass option" do
before do
# Filter the deprecation notice
allow($stdout).to receive(:puts)
end
it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :ask_sudo_pass, false it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :ask_sudo_pass, false
end end
describe "ask_vault_pass option" do describe "ask_vault_pass option" do

View File

@ -3,6 +3,9 @@ shared_examples_for 'options shared by both Ansible provisioners' do
it "assigns default values to unset common options" do it "assigns default values to unset common options" do
subject.finalize! subject.finalize!
expect(subject.become).to be(false)
expect(subject.become_user).to be_nil
expect(subject.compatibility_mode).to eql(VagrantPlugins::Ansible::COMPATIBILITY_MODE_AUTO)
expect(subject.config_file).to be_nil expect(subject.config_file).to be_nil
expect(subject.extra_vars).to be_nil expect(subject.extra_vars).to be_nil
expect(subject.galaxy_command).to eql("ansible-galaxy install --role-file=%{role_file} --roles-path=%{roles_path} --force") expect(subject.galaxy_command).to eql("ansible-galaxy install --role-file=%{role_file} --roles-path=%{roles_path} --force")
@ -17,11 +20,12 @@ shared_examples_for 'options shared by both Ansible provisioners' do
expect(subject.raw_arguments).to be_nil expect(subject.raw_arguments).to be_nil
expect(subject.skip_tags).to be_nil expect(subject.skip_tags).to be_nil
expect(subject.start_at_task).to be_nil expect(subject.start_at_task).to be_nil
expect(subject.sudo).to be(false) expect(subject.sudo).to be(false) # deprecated
expect(subject.sudo_user).to be_nil expect(subject.sudo_user).to be_nil # deprecated
expect(subject.tags).to be_nil expect(subject.tags).to be_nil
expect(subject.vault_password_file).to be_nil expect(subject.vault_password_file).to be_nil
expect(subject.verbose).to be(false) expect(subject.verbose).to be(false)
expect(subject.version).to be_empty
end end
end end
@ -41,6 +45,44 @@ shared_examples_for 'an Ansible provisioner' do | path_prefix, ansible_setup |
]) ])
end end
describe "compatibility_mode option" do
VagrantPlugins::Ansible::COMPATIBILITY_MODES.each do |valid_mode|
it "supports compatibility mode '#{valid_mode}'" do
subject.compatibility_mode = valid_mode
subject.finalize!
result = subject.validate(machine)
expect(subject.compatibility_mode).to eql(valid_mode)
end
end
it "returns an error if the compatibility mode is not set" do
subject.compatibility_mode = nil
subject.finalize!
result = subject.validate(machine)
expect(result[provisioner_label]).to eql([
I18n.t("vagrant.provisioners.ansible.errors.no_compatibility_mode",
valid_modes: "'auto', '1.8', '2.0'")
])
end
%w(invalid 1.9 2.3).each do |invalid_mode|
it "returns an error if the compatibility mode is invalid (e.g. '#{invalid_mode}')" do
subject.compatibility_mode = invalid_mode
subject.finalize!
result = subject.validate(machine)
expect(result[provisioner_label]).to eql([
I18n.t("vagrant.provisioners.ansible.errors.no_compatibility_mode",
valid_modes: "'auto', '1.8', '2.0'")
])
end
end
end
it "passes if the extra_vars option is a hash" do it "passes if the extra_vars option is a hash" do
subject.extra_vars = { var1: 1, var2: "foo" } subject.extra_vars = { var1: 1, var2: "foo" }
subject.finalize! subject.finalize!
@ -82,6 +124,7 @@ shared_examples_for 'an Ansible provisioner' do | path_prefix, ansible_setup |
end end
it "it collects and returns all detected errors" do it "it collects and returns all detected errors" do
subject.compatibility_mode = nil
subject.playbook = nil subject.playbook = nil
subject.extra_vars = ["var1", 3, "var2", 5] subject.extra_vars = ["var1", 3, "var2", 5]
subject.raw_arguments = { arg1: 1, arg2: "foo" } subject.raw_arguments = { arg1: 1, arg2: "foo" }
@ -89,7 +132,10 @@ shared_examples_for 'an Ansible provisioner' do | path_prefix, ansible_setup |
result = subject.validate(machine) result = subject.validate(machine)
expect(result[provisioner_label].size).to eql(3) expect(result[provisioner_label].size).to eql(4)
expect(result[provisioner_label]).to include(
I18n.t("vagrant.provisioners.ansible.errors.no_compatibility_mode",
valid_modes: "'auto', '1.8', '2.0'"))
expect(result[provisioner_label]).to include( expect(result[provisioner_label]).to include(
I18n.t("vagrant.provisioners.ansible.errors.no_playbook")) I18n.t("vagrant.provisioners.ansible.errors.no_playbook"))
expect(result[provisioner_label]).to include( expect(result[provisioner_label]).to include(
@ -102,7 +148,15 @@ shared_examples_for 'an Ansible provisioner' do | path_prefix, ansible_setup |
value: subject.raw_arguments.to_s)) value: subject.raw_arguments.to_s))
end end
describe "become option" do
it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :become, false
end
describe "sudo option" do describe "sudo option" do
before do
# Filter the deprecation notice
allow($stdout).to receive(:puts)
end
it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :sudo, false it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :sudo, false
end end

View File

@ -60,6 +60,8 @@ VF
stubbed_ui = Vagrant::UI::Colored.new stubbed_ui = Vagrant::UI::Colored.new
allow(stubbed_ui).to receive(:detail).and_return("") allow(stubbed_ui).to receive(:detail).and_return("")
allow(stubbed_ui).to receive(:warn).and_return("")
allow(machine.env).to receive(:ui).and_return(stubbed_ui) allow(machine.env).to receive(:ui).and_return(stubbed_ui)
config.playbook = 'playbook.yml' config.playbook = 'playbook.yml'
@ -69,6 +71,15 @@ VF
# Class methods for code reuse across examples # Class methods for code reuse across examples
# #
def self.it_should_check_ansible_version()
it "execute 'ansible --version' before executing 'ansible-playbook'" do
expect(Vagrant::Util::Subprocess).to receive(:execute).
once.with('ansible', '--version', { :notify => [:stdout, :stderr] })
expect(Vagrant::Util::Subprocess).to receive(:execute).
once.with('ansible-playbook', any_args)
end
end
def self.it_should_set_arguments_and_environment_variables( def self.it_should_set_arguments_and_environment_variables(
expected_args_count = 5, expected_args_count = 5,
expected_vars_count = 4, expected_vars_count = 4,
@ -76,9 +87,7 @@ VF
expected_transport_mode = "ssh") expected_transport_mode = "ssh")
it "sets implicit arguments in a specific order" do it "sets implicit arguments in a specific order" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
expect(args[0]).to eq("ansible-playbook")
expect(args[1]).to eq("--connection=ssh") expect(args[1]).to eq("--connection=ssh")
expect(args[2]).to eq("--timeout=30") expect(args[2]).to eq("--timeout=30")
@ -90,7 +99,7 @@ VF
end end
it "sets --limit argument" do it "sets --limit argument" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
all_limits = args.select { |x| x =~ /^(--limit=|-l)/ } all_limits = args.select { |x| x =~ /^(--limit=|-l)/ }
if config.raw_arguments if config.raw_arguments
raw_limits = config.raw_arguments.select { |x| x =~ /^(--limit=|-l)/ } raw_limits = config.raw_arguments.select { |x| x =~ /^(--limit=|-l)/ }
@ -108,7 +117,7 @@ VF
end end
it "exports environment variables" do it "exports environment variables" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
cmd_opts = args.last cmd_opts = args.last
if expected_host_key_checking if expected_host_key_checking
@ -116,6 +125,7 @@ VF
else else
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o UserKnownHostsFile=/dev/null") expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o UserKnownHostsFile=/dev/null")
end end
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentitiesOnly=yes") expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentitiesOnly=yes")
expect(cmd_opts[:env]['ANSIBLE_FORCE_COLOR']).to eql("true") expect(cmd_opts[:env]['ANSIBLE_FORCE_COLOR']).to eql("true")
expect(cmd_opts[:env]).to_not include("ANSIBLE_NOCOLOR") expect(cmd_opts[:env]).to_not include("ANSIBLE_NOCOLOR")
@ -126,14 +136,14 @@ VF
# "roughly" verify that only expected args/vars have been defined by the provisioner # "roughly" verify that only expected args/vars have been defined by the provisioner
it "sets the expected number of arguments and environment variables" do it "sets the expected number of arguments and environment variables" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
expect(args.length-2).to eq(expected_args_count) expect(args.length - 2).to eq(expected_args_count)
expect(args.last[:env].length).to eq(expected_vars_count) expect(args.last[:env].length).to eq(expected_vars_count)
}.and_return(default_execute_result) }.and_return(default_execute_result)
end end
it "enables '#{expected_transport_mode}' as default transport mode" do it "enables '#{expected_transport_mode}' as default transport mode" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
index = args.rindex("--connection=#{expected_transport_mode}") index = args.rindex("--connection=#{expected_transport_mode}")
expect(index).to be > 0 expect(index).to be > 0
expect(find_last_argument_after(index, args, /--connection=\w+/)).to be(false) expect(find_last_argument_after(index, args, /--connection=\w+/)).to be(false)
@ -144,7 +154,7 @@ VF
def self.it_should_set_optional_arguments(arg_map) def self.it_should_set_optional_arguments(arg_map)
it "sets optional arguments" do it "sets optional arguments" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
arg_map.each_pair do |vagrant_option, ansible_argument| arg_map.each_pair do |vagrant_option, ansible_argument|
index = args.index(ansible_argument) index = args.index(ansible_argument)
if config.send(vagrant_option) if config.send(vagrant_option)
@ -159,7 +169,7 @@ VF
def self.it_should_explicitly_enable_ansible_ssh_control_persist_defaults def self.it_should_explicitly_enable_ansible_ssh_control_persist_defaults
it "configures ControlPersist (like Ansible defaults) via ANSIBLE_SSH_ARGS" do it "configures ControlPersist (like Ansible defaults) via ANSIBLE_SSH_ARGS" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
cmd_opts = args.last cmd_opts = args.last
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o ControlMaster=auto") expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o ControlMaster=auto")
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o ControlPersist=60s") expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o ControlPersist=60s")
@ -167,23 +177,24 @@ VF
end end
end end
def self.it_should_create_and_use_generated_inventory(with_ssh_user = true) def self.it_should_create_and_use_generated_inventory(with_user = true)
it "generates an inventory with all active machines" do it "generates an inventory with all active machines" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
expect(config.inventory_path).to be_nil expect(config.inventory_path).to be_nil
expect(File.exists?(generated_inventory_file)).to be(true) expect(File.exists?(generated_inventory_file)).to be(true)
inventory_content = File.read(generated_inventory_file) inventory_content = File.read(generated_inventory_file)
if with_ssh_user _ssh = config.compatibility_mode == VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0 ? "" : "_ssh"
expect(inventory_content).to include("#{machine.name} ansible_ssh_host=#{machine.ssh_info[:host]} ansible_ssh_port=#{machine.ssh_info[:port]} ansible_ssh_user='#{machine.ssh_info[:username]}' ansible_ssh_private_key_file='#{machine.ssh_info[:private_key_path][0]}'\n") if with_user
expect(inventory_content).to include("#{machine.name} ansible#{_ssh}_host=#{machine.ssh_info[:host]} ansible#{_ssh}_port=#{machine.ssh_info[:port]} ansible#{_ssh}_user='#{machine.ssh_info[:username]}' ansible_ssh_private_key_file='#{machine.ssh_info[:private_key_path][0]}'\n")
else else
expect(inventory_content).to include("#{machine.name} ansible_ssh_host=#{machine.ssh_info[:host]} ansible_ssh_port=#{machine.ssh_info[:port]} ansible_ssh_private_key_file='#{machine.ssh_info[:private_key_path][0]}'\n") expect(inventory_content).to include("#{machine.name} ansible#{_ssh}_host=#{machine.ssh_info[:host]} ansible#{_ssh}_port=#{machine.ssh_info[:port]} ansible_ssh_private_key_file='#{machine.ssh_info[:private_key_path][0]}'\n")
end end
expect(inventory_content).to include("# MISSING: '#{iso_env.machine_names[1]}' machine was probably removed without using Vagrant. This machine should be recreated.\n") expect(inventory_content).to include("# MISSING: '#{iso_env.machine_names[1]}' machine was probably removed without using Vagrant. This machine should be recreated.\n")
}.and_return(default_execute_result) }.and_return(default_execute_result)
end end
it "sets as ansible inventory the directory containing the auto-generated inventory file" do it "sets as ansible inventory the directory containing the auto-generated inventory file" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
inventory_index = args.rindex("--inventory-file=#{generated_inventory_dir}") inventory_index = args.rindex("--inventory-file=#{generated_inventory_dir}")
expect(inventory_index).to be > 0 expect(inventory_index).to be > 0
expect(find_last_argument_after(inventory_index, args, /--inventory-file=\w+/)).to be(false) expect(find_last_argument_after(inventory_index, args, /--inventory-file=\w+/)).to be(false)
@ -260,11 +271,12 @@ VF
end end
describe "with default options" do describe "with default options" do
it_should_check_ansible_version
it_should_set_arguments_and_environment_variables it_should_set_arguments_and_environment_variables
it_should_create_and_use_generated_inventory it_should_create_and_use_generated_inventory
it "does not add any group section to the generated inventory" do it "does not add any group section to the generated inventory" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) {
inventory_content = File.read(generated_inventory_file) inventory_content = File.read(generated_inventory_file)
expect(inventory_content).to_not match(/^\s*\[^\\+\]\s*$/) expect(inventory_content).to_not match(/^\s*\[^\\+\]\s*$/)
}.and_return(default_execute_result) }.and_return(default_execute_result)
@ -275,14 +287,152 @@ VF
end end
end end
describe "deprecated 'sudo' options are aliases for equivalent 'become' options" do
before do
# Filter the deprecation notices
allow($stdout).to receive(:puts)
config.sudo = true
config.sudo_user = 'deployer'
config.ask_sudo_pass = true
end
it_should_set_optional_arguments({"sudo" => "--sudo",
"sudo_user" => "--sudo-user=deployer",
"ask_sudo_pass" => "--ask-sudo-pass",
"become" => "--sudo",
"become_user" => "--sudo-user=deployer",
"ask_become_pass" => "--ask-sudo-pass"})
end
context "with compatibility_mode 'auto'" do
before do
config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_AUTO
end
valid_versions = {
"0.6": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8,
"1.9.4": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8,
"2.5.0.0-rc1": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0,
"2.x.y.z": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0,
"4.3.2.1": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0,
}
valid_versions.each_pair do |ansible_version, mode|
describe "and ansible version #{ansible_version}" do
before do
allow(subject).to receive(:gather_ansible_version).and_return("ansible #{ansible_version}\n...\n")
end
it "detects the compatibility mode #{mode}" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
expect(config.compatibility_mode).to eq(mode)
}.and_return(default_execute_result)
end
it "warns about compatibility mode auto-detection being used" do
expect(machine.env.ui).to receive(:warn).with(
I18n.t("vagrant.provisioners.ansible.compatibility_mode_warning",
compatibility_mode: mode, ansible_version: ansible_version) +
"\n")
end
end
end
invalid_versions = [
"ansible devel",
"anything 1.2",
"2.9.2.1",
]
invalid_versions.each do |unknown_ansible_version|
describe "and `ansible --version` returning '#{unknown_ansible_version}'" do
before do
allow(subject).to receive(:gather_ansible_version).and_return(unknown_ansible_version)
end
it "applies the safest compatibility mode ('#{VagrantPlugins::Ansible::SAFE_COMPATIBILITY_MODE}')" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
expect(config.compatibility_mode).to eq(VagrantPlugins::Ansible::SAFE_COMPATIBILITY_MODE)
}.and_return(default_execute_result)
end
it "warns about not being able to detect the best compatibility mode" do
expect(machine.env.ui).to receive(:warn).with(
I18n.t("vagrant.provisioners.ansible.compatibility_mode_not_detected",
compatibility_mode: VagrantPlugins::Ansible::SAFE_COMPATIBILITY_MODE,
gathered_version: unknown_ansible_version) +
"\n")
end
end
end
end
context "with compatibility_mode '#{VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8}'" do
before do
config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8
end
it_should_check_ansible_version
it_should_create_and_use_generated_inventory
it "doesn't warn about compatibility mode auto-detection" do
expect(machine.env.ui).to_not receive(:warn)
end
end
context "with compatibility_mode '#{VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0}'" do
before do
config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0
allow(subject).to receive(:gather_ansible_version).and_return("ansible 2.3.0.0\n...\n")
end
it_should_create_and_use_generated_inventory
it "doesn't warn about compatibility mode auto-detection" do
expect(machine.env.ui).to_not receive(:warn)
end
describe "and an incompatible ansible version" do
before do
allow(subject).to receive(:gather_ansible_version).and_return("ansible 1.9.3\n...\n")
end
it "raises a compatibility conflict error", skip_before: false, skip_after: true do
expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleCompatibilityModeConflict)
end
end
describe "deprecated 'sudo' options are aliases for equivalent 'become' options" do
before do
# Filter the deprecation notices
allow($stdout).to receive(:puts)
config.sudo = true
config.sudo_user = 'deployer'
config.ask_sudo_pass = true
end
it_should_set_optional_arguments({"sudo" => "--become",
"sudo_user" => "--become-user=deployer",
"ask_sudo_pass" => "--ask-become-pass",
"become" => "--become",
"become_user" => "--become-user=deployer",
"ask_become_pass" => "--ask-become-pass"})
end
end
describe "with playbook_command option" do describe "with playbook_command option" do
before do before do
config.playbook_command = "custom-ansible-playbook" config.playbook_command = "custom-ansible-playbook"
# set the compatibility mode to ensure that only ansible-playbook is excuted
config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8
end end
it "uses custom playbook_command to run playbooks" do it "uses custom playbook_command to run playbooks" do
expect(Vagrant::Util::Subprocess).to receive(:execute) expect(Vagrant::Util::Subprocess).to receive(:execute)
.with("custom-ansible-playbook", any_args) .with("custom-ansible-playbook", any_args)
.and_return(default_execute_result)
end end
end end
@ -291,11 +441,15 @@ VF
it "adds host variables (given in Hash format) to the generated inventory" do it "adds host variables (given in Hash format) to the generated inventory" do
config.host_vars = { config.host_vars = {
machine1: {"http_port" => 80, "comments" => "'some text with spaces'"} machine1: {
"http_port" => 80,
"comments" => "'some text with spaces and quotes'",
"description" => "text with spaces but no quotes",
}
} }
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) {
inventory_content = File.read(generated_inventory_file) inventory_content = File.read(generated_inventory_file)
expect(inventory_content).to match("^" + Regexp.quote(machine.name) + ".+http_port=80 comments='some text with spaces'$") expect(inventory_content).to match("^" + Regexp.quote(machine.name) + ".+http_port=80 comments='some text with spaces and quotes' description='text with spaces but no quotes'")
}.and_return(default_execute_result) }.and_return(default_execute_result)
end end
@ -303,7 +457,8 @@ VF
config.host_vars = { config.host_vars = {
machine1: ["http_port=80", "maxRequestsPerChild=808"] machine1: ["http_port=80", "maxRequestsPerChild=808"]
} }
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) {
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) {
inventory_content = File.read(generated_inventory_file) inventory_content = File.read(generated_inventory_file)
expect(inventory_content).to match("^" + Regexp.quote(machine.name) + ".+http_port=80 maxRequestsPerChild=808") expect(inventory_content).to match("^" + Regexp.quote(machine.name) + ".+http_port=80 maxRequestsPerChild=808")
}.and_return(default_execute_result) }.and_return(default_execute_result)
@ -313,7 +468,8 @@ VF
config.host_vars = { config.host_vars = {
:machine1 => "http_port=80 maxRequestsPerChild=808" :machine1 => "http_port=80 maxRequestsPerChild=808"
} }
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) {
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) {
inventory_content = File.read(generated_inventory_file) inventory_content = File.read(generated_inventory_file)
expect(inventory_content).to match("^" + Regexp.quote(machine.name) + ".+http_port=80 maxRequestsPerChild=808") expect(inventory_content).to match("^" + Regexp.quote(machine.name) + ".+http_port=80 maxRequestsPerChild=808")
}.and_return(default_execute_result) }.and_return(default_execute_result)
@ -323,7 +479,8 @@ VF
config.host_vars = { config.host_vars = {
"machine1" => "http_port=80 maxRequestsPerChild=808" "machine1" => "http_port=80 maxRequestsPerChild=808"
} }
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) {
inventory_content = File.read(generated_inventory_file) inventory_content = File.read(generated_inventory_file)
expect(inventory_content).to match("^" + Regexp.quote(machine.name) + ".+http_port=80 maxRequestsPerChild=808") expect(inventory_content).to match("^" + Regexp.quote(machine.name) + ".+http_port=80 maxRequestsPerChild=808")
}.and_return(default_execute_result) }.and_return(default_execute_result)
@ -345,7 +502,7 @@ VF
"bar:children" => ["group1", "group2", "group3", "group5"], "bar:children" => ["group1", "group2", "group3", "group5"],
} }
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
inventory_content = File.read(generated_inventory_file) inventory_content = File.read(generated_inventory_file)
# Accept String instead of Array for group member list # Accept String instead of Array for group member list
@ -383,7 +540,7 @@ VF
"group3:vars" => "stringvar1=stringvalue1 stringvar2=stringvalue2", "group3:vars" => "stringvar1=stringvalue1 stringvar2=stringvalue2",
} }
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
inventory_content = File.read(generated_inventory_file) inventory_content = File.read(generated_inventory_file)
# Hash syntax # Hash syntax
@ -396,6 +553,19 @@ VF
expect(inventory_content).to include("[group3:vars]\nstringvar1=stringvalue1\nstringvar2=stringvalue2\n") expect(inventory_content).to include("[group3:vars]\nstringvar1=stringvalue1\nstringvar2=stringvalue2\n")
}.and_return(default_execute_result) }.and_return(default_execute_result)
end end
it "adds 'all:vars' section to the generated inventory" do
config.groups = {
"all:vars" => { "var1" => "value1", "var2" => "value2" }
}
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
inventory_content = File.read(generated_inventory_file)
expect(inventory_content).to include("[all:vars]\nvar1=value1\nvar2=value2\n")
}.and_return(default_execute_result)
end
end end
describe "with host_key_checking option enabled" do describe "with host_key_checking option enabled" do
@ -408,18 +578,18 @@ VF
describe "with boolean (flag) options disabled" do describe "with boolean (flag) options disabled" do
before do before do
config.sudo = false config.become = false
config.ask_sudo_pass = false config.ask_become_pass = false
config.ask_vault_pass = false config.ask_vault_pass = false
config.sudo_user = 'root' config.become_user = 'root'
end end
it_should_set_arguments_and_environment_variables 6 it_should_set_arguments_and_environment_variables 6
it_should_set_optional_arguments({ "sudo_user" => "--sudo-user=root" }) it_should_set_optional_arguments({ "become_user" => "--sudo-user=root" })
it "it does not set boolean flag when corresponding option is set to false" do it "it does not set boolean flag when corresponding option is set to false" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
expect(args.index("--sudo")).to be_nil expect(args.index("--sudo")).to be_nil
expect(args.index("--ask-sudo-pass")).to be_nil expect(args.index("--ask-sudo-pass")).to be_nil
expect(args.index("--ask-vault-pass")).to be_nil expect(args.index("--ask-vault-pass")).to be_nil
@ -429,7 +599,7 @@ VF
describe "with raw_arguments option" do describe "with raw_arguments option" do
before do before do
config.sudo = false config.become = false
config.force_remote_user = false config.force_remote_user = false
config.skip_tags = %w(foo bar) config.skip_tags = %w(foo bar)
config.limit = "all" config.limit = "all"
@ -448,7 +618,7 @@ VF
it_should_set_arguments_and_environment_variables 17, 4, false, "paramiko" it_should_set_arguments_and_environment_variables 17, 4, false, "paramiko"
it "sets all raw arguments" do it "sets all raw arguments" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
config.raw_arguments.each do |raw_arg| config.raw_arguments.each do |raw_arg|
expect(args).to include(raw_arg) expect(args).to include(raw_arg)
end end
@ -456,7 +626,7 @@ VF
end end
it "sets raw arguments after arguments related to supported options" do it "sets raw arguments after arguments related to supported options" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
expect(args.index("--user=lion")).to be > args.index("--user=testuser") expect(args.index("--user=lion")).to be > args.index("--user=testuser")
expect(args.index("--inventory-file=/forget/it/my/friend")).to be > args.index("--inventory-file=#{generated_inventory_dir}") expect(args.index("--inventory-file=/forget/it/my/friend")).to be > args.index("--inventory-file=#{generated_inventory_dir}")
expect(args.index("--limit=bar")).to be > args.index("--limit=all") expect(args.index("--limit=bar")).to be > args.index("--limit=all")
@ -465,7 +635,7 @@ VF
end end
it "sets boolean flag (e.g. --sudo) defined in raw_arguments, even if corresponding option is set to false" do it "sets boolean flag (e.g. --sudo) defined in raw_arguments, even if corresponding option is set to false" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
expect(args).to include('--sudo') expect(args).to include('--sudo')
}.and_return(default_execute_result) }.and_return(default_execute_result)
end end
@ -490,7 +660,7 @@ VF
it_should_set_arguments_and_environment_variables 6 it_should_set_arguments_and_environment_variables 6
it "uses a --user argument to set a default remote user" do it "uses a --user argument to set a default remote user" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
expect(args).not_to include("--extra-vars=ansible_ssh_user='#{machine.ssh_info[:username]}'") expect(args).not_to include("--extra-vars=ansible_ssh_user='#{machine.ssh_info[:username]}'")
expect(args).to include("--user=#{machine.ssh_info[:username]}") expect(args).to include("--user=#{machine.ssh_info[:username]}")
}.and_return(default_execute_result) }.and_return(default_execute_result)
@ -521,8 +691,7 @@ VF
it_should_set_arguments_and_environment_variables it_should_set_arguments_and_environment_variables
it "generates an inventory with winrm connection settings" do it "generates an inventory with winrm connection settings" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
expect(config.inventory_path).to be_nil expect(config.inventory_path).to be_nil
expect(File.exists?(generated_inventory_file)).to be(true) expect(File.exists?(generated_inventory_file)).to be(true)
inventory_content = File.read(generated_inventory_file) inventory_content = File.read(generated_inventory_file)
@ -537,7 +706,7 @@ VF
end end
it "doesn't set the ansible remote user in inventory and use '--user' argument with the vagrant ssh username" do it "doesn't set the ansible remote user in inventory and use '--user' argument with the vagrant ssh username" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
inventory_content = File.read(generated_inventory_file) inventory_content = File.read(generated_inventory_file)
expect(inventory_content).to include("machine1 ansible_connection=winrm ansible_ssh_host=127.0.0.1 ansible_ssh_port=55986 ansible_ssh_pass='winword'\n") expect(inventory_content).to include("machine1 ansible_connection=winrm ansible_ssh_host=127.0.0.1 ansible_ssh_port=55986 ansible_ssh_pass='winword'\n")
@ -555,7 +724,7 @@ VF
it_should_set_arguments_and_environment_variables 6 it_should_set_arguments_and_environment_variables 6
it "does not generate the inventory and uses given inventory path instead" do it "does not generate the inventory and uses given inventory path instead" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
expect(args).to include("--inventory-file=#{existing_file}") expect(args).to include("--inventory-file=#{existing_file}")
expect(args).not_to include("--inventory-file=#{generated_inventory_file}") expect(args).not_to include("--inventory-file=#{generated_inventory_file}")
expect(File.exists?(generated_inventory_file)).to be(false) expect(File.exists?(generated_inventory_file)).to be(false)
@ -563,7 +732,7 @@ VF
end end
it "uses an --extra-vars argument to force ansible_ssh_user parameter" do it "uses an --extra-vars argument to force ansible_ssh_user parameter" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
expect(args).not_to include("--user=#{machine.ssh_info[:username]}") expect(args).not_to include("--user=#{machine.ssh_info[:username]}")
expect(args).to include("--extra-vars=ansible_ssh_user='#{machine.ssh_info[:username]}'") expect(args).to include("--extra-vars=ansible_ssh_user='#{machine.ssh_info[:username]}'")
}.and_return(default_execute_result) }.and_return(default_execute_result)
@ -575,7 +744,7 @@ VF
end end
it "uses a --user argument to set a default remote user" do it "uses a --user argument to set a default remote user" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
expect(args).not_to include("--extra-vars=ansible_ssh_user='#{machine.ssh_info[:username]}'") expect(args).not_to include("--extra-vars=ansible_ssh_user='#{machine.ssh_info[:username]}'")
expect(args).to include("--user=#{machine.ssh_info[:username]}") expect(args).to include("--user=#{machine.ssh_info[:username]}")
}.and_return(default_execute_result) }.and_return(default_execute_result)
@ -589,7 +758,7 @@ VF
end end
it "sets ANSIBLE_CONFIG environment variable" do it "sets ANSIBLE_CONFIG environment variable" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
cmd_opts = args.last cmd_opts = args.last
expect(cmd_opts[:env]).to include("ANSIBLE_CONFIG") expect(cmd_opts[:env]).to include("ANSIBLE_CONFIG")
expect(cmd_opts[:env]['ANSIBLE_CONFIG']).to eql(existing_file) expect(cmd_opts[:env]['ANSIBLE_CONFIG']).to eql(existing_file)
@ -605,7 +774,7 @@ VF
it_should_set_arguments_and_environment_variables 6 it_should_set_arguments_and_environment_variables 6
it "should ask the vault password" do it "should ask the vault password" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
expect(args).to include("--ask-vault-pass") expect(args).to include("--ask-vault-pass")
}.and_return(default_execute_result) }.and_return(default_execute_result)
end end
@ -619,7 +788,7 @@ VF
it_should_set_arguments_and_environment_variables 6 it_should_set_arguments_and_environment_variables 6
it "uses the given vault password file" do it "uses the given vault password file" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
expect(args).to include("--vault-password-file=#{existing_file}") expect(args).to include("--vault-password-file=#{existing_file}")
}.and_return(default_execute_result) }.and_return(default_execute_result)
end end
@ -634,7 +803,7 @@ VF
it_should_explicitly_enable_ansible_ssh_control_persist_defaults it_should_explicitly_enable_ansible_ssh_control_persist_defaults
it "passes custom SSH options via ANSIBLE_SSH_ARGS with the highest priority" do it "passes custom SSH options via ANSIBLE_SSH_ARGS with the highest priority" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
cmd_opts = args.last cmd_opts = args.last
raw_opt_index = cmd_opts[:env]['ANSIBLE_SSH_ARGS'].index("-o ControlMaster=no") raw_opt_index = cmd_opts[:env]['ANSIBLE_SSH_ARGS'].index("-o ControlMaster=no")
default_opt_index = cmd_opts[:env]['ANSIBLE_SSH_ARGS'].index("-o ControlMaster=auto") default_opt_index = cmd_opts[:env]['ANSIBLE_SSH_ARGS'].index("-o ControlMaster=auto")
@ -648,7 +817,7 @@ VF
end end
it "sets '-o ForwardAgent=yes' via ANSIBLE_SSH_ARGS with higher priority than raw_ssh_args values" do it "sets '-o ForwardAgent=yes' via ANSIBLE_SSH_ARGS with higher priority than raw_ssh_args values" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
cmd_opts = args.last cmd_opts = args.last
forwardAgentYes = cmd_opts[:env]['ANSIBLE_SSH_ARGS'].index("-o ForwardAgent=yes") forwardAgentYes = cmd_opts[:env]['ANSIBLE_SSH_ARGS'].index("-o ForwardAgent=yes")
forwardAgentNo = cmd_opts[:env]['ANSIBLE_SSH_ARGS'].index("-o ForwardAgent=no") forwardAgentNo = cmd_opts[:env]['ANSIBLE_SSH_ARGS'].index("-o ForwardAgent=no")
@ -668,7 +837,7 @@ VF
it_should_explicitly_enable_ansible_ssh_control_persist_defaults it_should_explicitly_enable_ansible_ssh_control_persist_defaults
it "passes additional Identity Files via ANSIBLE_SSH_ARGS" do it "passes additional Identity Files via ANSIBLE_SSH_ARGS" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
cmd_opts = args.last cmd_opts = args.last
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentityFile=/an/other/identity") expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentityFile=/an/other/identity")
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentityFile=/yet/an/other/key") expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentityFile=/yet/an/other/key")
@ -682,7 +851,7 @@ VF
end end
it "replaces `%` with `%%`" do it "replaces `%` with `%%`" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
cmd_opts = args.last cmd_opts = args.last
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentityFile=/foo%%bar/key") expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentityFile=/foo%%bar/key")
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentityFile=/bar%%%%buz/key") expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentityFile=/bar%%%%buz/key")
@ -699,7 +868,7 @@ VF
it_should_explicitly_enable_ansible_ssh_control_persist_defaults it_should_explicitly_enable_ansible_ssh_control_persist_defaults
it "enables SSH-Forwarding via ANSIBLE_SSH_ARGS" do it "enables SSH-Forwarding via ANSIBLE_SSH_ARGS" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
cmd_opts = args.last cmd_opts = args.last
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o ForwardAgent=yes") expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o ForwardAgent=yes")
}.and_return(default_execute_result) }.and_return(default_execute_result)
@ -712,7 +881,7 @@ VF
end end
it "sets '-o ProxyCommand' via ANSIBLE_SSH_ARGS" do it "sets '-o ProxyCommand' via ANSIBLE_SSH_ARGS" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
cmd_opts = args.last cmd_opts = args.last
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o ProxyCommand='ssh -W %h:%p -q user@remote_libvirt_host'") expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o ProxyCommand='ssh -W %h:%p -q user@remote_libvirt_host'")
}.and_return(default_execute_result) }.and_return(default_execute_result)
@ -782,10 +951,12 @@ VF
describe "without colorized output" do describe "without colorized output" do
before do before do
allow(machine.env).to receive(:ui).and_return(Vagrant::UI::Basic.new) allow(machine.env).to receive(:ui).and_return(Vagrant::UI::Basic.new)
allow(machine.env.ui).to receive(:warn).and_return("") # hide the breaking change warning
end end
it "disables ansible-playbook colored output" do it "disables ansible-playbook colored output" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
cmd_opts = args.last cmd_opts = args.last
expect(cmd_opts[:env]).to_not include("ANSIBLE_FORCE_COLOR") expect(cmd_opts[:env]).to_not include("ANSIBLE_FORCE_COLOR")
expect(cmd_opts[:env]['ANSIBLE_NOCOLOR']).to eql("true") expect(cmd_opts[:env]['ANSIBLE_NOCOLOR']).to eql("true")
@ -793,6 +964,54 @@ VF
end end
end end
context "with version option set" do
before do
config.version = "2.3.4.5"
end
describe "and the installed ansible version is correct" do
before do
allow(subject).to receive(:gather_ansible_version).and_return("ansible #{config.version}\n...\n")
end
it "executes ansible-playbook command" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args).and_return(default_execute_result)
end
end
describe "and there is an ansible version mismatch" do
before do
allow(subject).to receive(:gather_ansible_version).and_return("ansible 1.9.6\n...\n")
end
it "raises an error about the ansible version mismatch", skip_before: false, skip_after: true do
expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleVersionMismatch)
end
end
describe "and the installed ansible version cannot be detected" do
before do
allow(subject).to receive(:gather_ansible_version).and_return("")
end
it "skips the ansible version check and executes ansible-playbook command" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args).and_return(default_execute_result)
end
end
describe "with special value: 'latest'" do
before do
config.version = :latest
allow(subject).to receive(:gather_ansible_version).and_return("ansible 2.2.0.1\n...\n")
end
it "skips the ansible version check and executes ansible-playbook command" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args).and_return(default_execute_result)
end
end
end
describe "with galaxy support" do describe "with galaxy support" do
before do before do
@ -809,7 +1028,11 @@ VF
expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleCommandFailed) expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleCommandFailed)
end end
it "execute ansible-galaxy, and then ansible-playbook" do it "execute three commands: ansible --version, ansible-galaxy, and ansible-playbook" do
expect(Vagrant::Util::Subprocess).to receive(:execute)
.once
.with('ansible', '--version', { :notify => [:stdout, :stderr] })
.and_return(default_execute_result)
expect(Vagrant::Util::Subprocess).to receive(:execute) expect(Vagrant::Util::Subprocess).to receive(:execute)
.once .once
.with('ansible-galaxy', any_args) .with('ansible-galaxy', any_args)
@ -843,7 +1066,7 @@ VF
end end
it "sets ANSIBLE_ROLES_PATH with corresponding absolute path" do it "sets ANSIBLE_ROLES_PATH with corresponding absolute path" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
cmd_opts = args.last cmd_opts = args.last
expect(cmd_opts[:env]).to include("ANSIBLE_ROLES_PATH") expect(cmd_opts[:env]).to include("ANSIBLE_ROLES_PATH")
expect(cmd_opts[:env]['ANSIBLE_ROLES_PATH']).to eql(File.join(machine.env.root_path, "my-roles")) expect(cmd_opts[:env]['ANSIBLE_ROLES_PATH']).to eql(File.join(machine.env.root_path, "my-roles"))
@ -854,10 +1077,10 @@ VF
context "with extra_vars option defined" do context "with extra_vars option defined" do
describe "with a hash value" do describe "with a hash value" do
before do before do
config.extra_vars = { var1: %Q(string with 'apostrophes', \\, " and =), var2: { x: 42 } } config.extra_vars = { var1: %Q(string with 'apo$trophe$', \\, " and =), var2: { x: 42 } }
end end
it_should_set_optional_arguments({ "extra_vars" => "--extra-vars={\"var1\":\"string with 'apostrophes', \\\\, \\\" and =\",\"var2\":{\"x\":42}}" }) it_should_set_optional_arguments({ "extra_vars" => "--extra-vars={\"var1\":\"string with 'apo$trophe$', \\\\, \\\" and =\",\"var2\":{\"x\":42}}" })
end end
describe "with a string value referring to file path (with the '@' prefix)" do describe "with a string value referring to file path (with the '@' prefix)" do
@ -879,11 +1102,11 @@ VF
# command line arguments # command line arguments
config.galaxy_roles_path = "/up/to the stars" config.galaxy_roles_path = "/up/to the stars"
config.extra_vars = { var1: %Q(string with 'apostrophes', \\, " and =), var2: { x: 42 } } config.extra_vars = { var1: %Q(string with 'apo$trophe$', \\, " and =), var2: { x: 42 } }
config.sudo = true config.become = true
config.sudo_user = 'deployer' config.become_user = 'deployer'
config.verbose = "vvv" config.verbose = "vvv"
config.ask_sudo_pass = true config.ask_become_pass = true
config.ask_vault_pass = true config.ask_vault_pass = true
config.vault_password_file = existing_file config.vault_password_file = existing_file
config.tags = %w(db www) config.tags = %w(db www)
@ -900,11 +1123,11 @@ VF
it_should_set_arguments_and_environment_variables 21, 6, true it_should_set_arguments_and_environment_variables 21, 6, true
it_should_explicitly_enable_ansible_ssh_control_persist_defaults it_should_explicitly_enable_ansible_ssh_control_persist_defaults
it_should_set_optional_arguments({ "extra_vars" => "--extra-vars={\"var1\":\"string with 'apostrophes', \\\\, \\\" and =\",\"var2\":{\"x\":42}}", it_should_set_optional_arguments({ "extra_vars" => "--extra-vars={\"var1\":\"string with 'apo$trophe$', \\\\, \\\" and =\",\"var2\":{\"x\":42}}",
"sudo" => "--sudo", "become" => "--sudo",
"sudo_user" => "--sudo-user=deployer", "become_user" => "--sudo-user=deployer",
"verbose" => "-vvv", "verbose" => "-vvv",
"ask_sudo_pass" => "--ask-sudo-pass", "ask_become_pass" => "--ask-sudo-pass",
"ask_vault_pass" => "--ask-vault-pass", "ask_vault_pass" => "--ask-vault-pass",
"vault_password_file" => "--vault-password-file=#{File.expand_path(__FILE__)}", "vault_password_file" => "--vault-password-file=#{File.expand_path(__FILE__)}",
"tags" => "--tags=db,www", "tags" => "--tags=db,www",
@ -914,7 +1137,7 @@ VF
}) })
it "also includes given raw arguments" do it "also includes given raw arguments" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
expect(args).to include("--why-not") expect(args).to include("--why-not")
expect(args).to include("--su-user=foot") expect(args).to include("--su-user=foot")
expect(args).to include("--ask-su-pass") expect(args).to include("--ask-su-pass")
@ -925,7 +1148,7 @@ VF
it "shows the ansible-playbook command, with additional quotes when required" do it "shows the ansible-playbook command, with additional quotes when required" do
expect(machine.env.ui).to receive(:detail) expect(machine.env.ui).to receive(:detail)
.with(%Q(PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_ROLES_PATH='/up/to the stars' ANSIBLE_CONFIG='#{existing_file}' ANSIBLE_HOST_KEY_CHECKING=true ANSIBLE_SSH_ARGS='-o IdentitiesOnly=yes -o IdentityFile=/my/key1 -o IdentityFile=/my/key2 -o ForwardAgent=yes -o ControlMaster=no -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --ask-sudo-pass --ask-vault-pass --limit="machine*:&vagrant:!that_one" --inventory-file=#{generated_inventory_dir} --extra-vars="{\\"var1\\":\\"string with 'apostrophes', \\\\\\\\, \\\\\\" and =\\",\\"var2\\":{\\"x\\":42}}" --sudo --sudo-user=deployer -vvv --vault-password-file=#{existing_file} --tags=db,www --skip-tags=foo,bar --start-at-task="joe's awesome task" --why-not --su-user=foot --ask-su-pass --limit=all --private-key=./myself.key --extra-vars='{\"var3\":\"foo\"}' playbook.yml)) .with(%Q(PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_ROLES_PATH='/up/to the stars' ANSIBLE_CONFIG='#{existing_file}' ANSIBLE_HOST_KEY_CHECKING=true ANSIBLE_SSH_ARGS='-o IdentitiesOnly=yes -o IdentityFile=/my/key1 -o IdentityFile=/my/key2 -o ForwardAgent=yes -o ControlMaster=no -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --ask-sudo-pass --ask-vault-pass --limit="machine*:&vagrant:!that_one" --inventory-file=#{generated_inventory_dir} --extra-vars=\\{\\"var1\\":\\"string\\ with\\ \\'apo\\$trophe\\$\\',\\ \\\\\\\\,\\ \\\\\\"\\ and\\ \\=\\",\\"var2\\":\\{\\"x\\":42\\}\\} --sudo --sudo-user=deployer -vvv --vault-password-file=#{existing_file} --tags=db,www --skip-tags=foo,bar --start-at-task="joe's awesome task" --why-not --su-user=foot --ask-su-pass --limit=all --private-key=./myself.key --extra-vars='{\"var3\":\"foo\"}' playbook.yml))
end end
end end
@ -954,7 +1177,7 @@ VF
end end
it "uses an SSH ProxyCommand to reach the VM" do it "uses an SSH ProxyCommand to reach the VM" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
cmd_opts = args.last cmd_opts = args.last
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o ProxyCommand='ssh boot9docker@127.0.0.1 -p 2299 -i /path/to/docker/host/key -o Compression=yes -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no exec nc %h %p 2>/dev/null'") expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o ProxyCommand='ssh boot9docker@127.0.0.1 -p 2299 -i /path/to/docker/host/key -o Compression=yes -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no exec nc %h %p 2>/dev/null'")
}.and_return(default_execute_result) }.and_return(default_execute_result)
@ -969,11 +1192,14 @@ VF
before do before do
allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true) allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)
allow(machine.ui).to receive(:warn) allow(machine.ui).to receive(:warn)
# Set the compatibility mode to only get the Windows warning
config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8
end end
it "warns that Windows is not officially supported for the Ansible control machine" do it "warns that Windows is not officially supported for the Ansible control machine" do
expect(machine.env.ui).to receive(:warn) expect(machine.env.ui).to receive(:warn)
.with(I18n.t("vagrant.provisioners.ansible.windows_not_supported_for_control_machine")) .with(I18n.t("vagrant.provisioners.ansible.windows_not_supported_for_control_machine") + "\n")
end end
end end
@ -983,7 +1209,7 @@ VF
end end
it "does not set IdentitiesOnly=yes in ANSIBLE_SSH_ARGS" do it "does not set IdentitiesOnly=yes in ANSIBLE_SSH_ARGS" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
cmd_opts = args.last cmd_opts = args.last
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to_not include("-o IdentitiesOnly=yes") expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to_not include("-o IdentitiesOnly=yes")
}.and_return(default_execute_result) }.and_return(default_execute_result)
@ -993,7 +1219,7 @@ VF
it "does not set ANSIBLE_SSH_ARGS environment variable" do it "does not set ANSIBLE_SSH_ARGS environment variable" do
config.host_key_checking = true config.host_key_checking = true
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
cmd_opts = args.last cmd_opts = args.last
expect(cmd_opts[:env]).to_not include('ANSIBLE_SSH_ARGS') expect(cmd_opts[:env]).to_not include('ANSIBLE_SSH_ARGS')
}.and_return(Vagrant::Util::Subprocess::Result.new(0, "", "")) }.and_return(Vagrant::Util::Subprocess::Result.new(0, "", ""))
@ -1006,7 +1232,7 @@ VF
it 'does not set IdentitiesOnly=yes in ANSIBLE_SSH_ARGS' do it 'does not set IdentitiesOnly=yes in ANSIBLE_SSH_ARGS' do
ssh_info[:keys_only] = false ssh_info[:keys_only] = false
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
cmd_opts = args.last cmd_opts = args.last
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to_not include("-o IdentitiesOnly=yes") expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to_not include("-o IdentitiesOnly=yes")
}.and_return(default_execute_result) }.and_return(default_execute_result)

View File

@ -89,5 +89,39 @@ describe VagrantPlugins::FileUpload::Provisioner do
subject.provision subject.provision
end end
it "appends a '/.' if the destination doesnt end with a file separator" do
allow(config).to receive(:source).and_return("/source")
allow(config).to receive(:destination).and_return("/foo/bar")
allow(File).to receive(:directory?).with("/source").and_return(true)
expect(guest).to receive(:capability?).
with(:shell_expand_guest_path).and_return(true)
expect(guest).to receive(:capability).
with(:shell_expand_guest_path, "/foo/bar").and_return("/foo/bar")
expect(communicator).to receive(:upload).with("/source/.", "/foo/bar")
subject.provision
end
it "sends an array of files and folders if winrm and destination doesn't end with file separator" do
files = ["/source/file.py", "/source/folder"]
allow(Dir).to receive(:[]).and_return(files)
allow(config).to receive(:source).and_return("/source")
allow(config).to receive(:destination).and_return("/foo/bar")
allow(File).to receive(:directory?).with("/source").and_return(true)
allow(machine.config.vm).to receive(:communicator).and_return(:winrm)
expect(guest).to receive(:capability?).
with(:shell_expand_guest_path).and_return(true)
expect(guest).to receive(:capability).
with(:shell_expand_guest_path, "/foo/bar").and_return("/foo/bar")
expect(communicator).to receive(:upload)
.with(files, "/foo/bar")
subject.provision
end
end end
end end

View File

@ -78,5 +78,41 @@ describe VagrantPlugins::Salt::Config do
expect(result[error_key]).to be_empty expect(result[error_key]).to be_empty
end end
end end
context "salt_call_args" do
it "fails if salt_call_args is not an array" do
subject.salt_call_args = "--flags"
subject.finalize!
result = subject.validate(machine)
expect(result[error_key]).to_not be_empty
end
it "is valid if is set and not missing" do
subject.salt_call_args = ["--flags"]
subject.finalize!
result = subject.validate(machine)
expect(result[error_key]).to be_empty
end
end
context "salt_args" do
it "fails if not an array" do
subject.salt_args = "--flags"
subject.finalize!
result = subject.validate(machine)
expect(result[error_key]).to_not be_empty
end
it "is valid if is set and not missing" do
subject.salt_args = ["--flags"]
subject.finalize!
result = subject.validate(machine)
expect(result[error_key]).to be_empty
end
end
end end
end end

View File

@ -32,4 +32,89 @@ describe VagrantPlugins::Salt::Provisioner do
describe "#provision" do describe "#provision" do
end end
describe "#call_highstate" do
context "master" do
it "passes along extra cli flags" do
allow(config).to receive(:run_highstate).and_return(true)
allow(config).to receive(:verbose).and_return(true)
allow(config).to receive(:masterless?).and_return(false)
allow(config).to receive(:masterless).and_return(false)
allow(config).to receive(:minion_id).and_return(nil)
allow(config).to receive(:log_level).and_return(nil)
allow(config).to receive(:colorize).and_return(false)
allow(config).to receive(:pillar_data).and_return([])
allow(config).to receive(:install_master).and_return(true)
allow(config).to receive(:salt_args).and_return(["--async"])
allow(machine.communicate).to receive(:sudo)
allow(machine.config.vm).to receive(:communicator).and_return(:notwinrm)
expect(machine.communicate).to receive(:sudo).with("salt '*' state.highstate --verbose --log-level=debug --no-color --async", {:error_key=>:ssh_bad_exit_status_muted})
subject.call_highstate()
end
it "has no additional cli flags if not included" do
allow(config).to receive(:run_highstate).and_return(true)
allow(config).to receive(:verbose).and_return(true)
allow(config).to receive(:masterless?).and_return(false)
allow(config).to receive(:masterless).and_return(false)
allow(config).to receive(:minion_id).and_return(nil)
allow(config).to receive(:log_level).and_return(nil)
allow(config).to receive(:colorize).and_return(false)
allow(config).to receive(:pillar_data).and_return([])
allow(config).to receive(:install_master).and_return(true)
allow(config).to receive(:salt_args).and_return(nil)
allow(machine.communicate).to receive(:sudo)
allow(machine.config.vm).to receive(:communicator).and_return(:notwinrm)
expect(machine.communicate).to receive(:sudo).with("salt '*' state.highstate --verbose --log-level=debug --no-color ", {:error_key=>:ssh_bad_exit_status_muted})
subject.call_highstate()
end
end
context "with masterless" do
it "passes along extra cli flags" do
allow(config).to receive(:run_highstate).and_return(true)
allow(config).to receive(:verbose).and_return(true)
allow(config).to receive(:masterless?).and_return(true)
allow(config).to receive(:masterless).and_return(true)
allow(config).to receive(:minion_id).and_return(nil)
allow(config).to receive(:log_level).and_return(nil)
allow(config).to receive(:colorize).and_return(false)
allow(config).to receive(:pillar_data).and_return([])
allow(config).to receive(:salt_args).and_return(["--async"])
allow(config).to receive(:salt_call_args).and_return(["--output-dif"])
allow(machine.communicate).to receive(:sudo)
allow(machine.config.vm).to receive(:communicator).and_return(:notwinrm)
allow(config).to receive(:install_master).and_return(false)
expect(machine.communicate).to receive(:sudo).with("salt-call state.highstate --retcode-passthrough --local --log-level=debug --no-color --output-dif", {:error_key=>:ssh_bad_exit_status_muted})
subject.call_highstate()
end
it "has no additional cli flags if not included" do
allow(config).to receive(:run_highstate).and_return(true)
allow(config).to receive(:verbose).and_return(true)
allow(config).to receive(:masterless?).and_return(true)
allow(config).to receive(:masterless).and_return(true)
allow(config).to receive(:minion_id).and_return(nil)
allow(config).to receive(:log_level).and_return(nil)
allow(config).to receive(:colorize).and_return(false)
allow(config).to receive(:pillar_data).and_return([])
allow(config).to receive(:salt_call_args).and_return(nil)
allow(config).to receive(:salt_args).and_return(nil)
allow(machine.communicate).to receive(:sudo)
allow(machine.config.vm).to receive(:communicator).and_return(:notwinrm)
allow(config).to receive(:install_master).and_return(false)
expect(machine.communicate).to receive(:sudo).with("salt-call state.highstate --retcode-passthrough --local --log-level=debug --no-color ", {:error_key=>:ssh_bad_exit_status_muted})
subject.call_highstate()
end
end
end
end end

View File

@ -133,4 +133,27 @@ describe Vagrant::Util::Platform do
end end
end end
end end
describe ".systemd?" do
before{ allow(subject).to receive(:windows?).and_return(false) }
after{ subject.reset! }
context "on windows" do
before{ expect(subject).to receive(:windows?).and_return(true) }
it "should return false" do
expect(subject.systemd?).to be_falsey
end
end
it "should return true if systemd is in use" do
expect(Vagrant::Util::Subprocess).to receive(:execute).and_return(double(:result, stdout: "systemd"))
expect(subject.systemd?).to be_truthy
end
it "should return false if systemd is not in use" do
expect(Vagrant::Util::Subprocess).to receive(:execute).and_return(double(:result, stdout: "other"))
expect(subject.systemd?).to be_falsey
end
end
end end

View File

@ -67,7 +67,7 @@ describe Vagrant::Util::SSH do
expect(described_class.exec(ssh_info)).to eq(nil) expect(described_class.exec(ssh_info)).to eq(nil)
expect(Vagrant::Util::SafeExec).to have_received(:exec) expect(Vagrant::Util::SafeExec).to have_received(:exec)
.with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL","-o", "Compression=yes", "-o", "DSAAuthentication=yes", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=#{ssh_info[:private_key_path][0]}") .with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL","-o", "Compression=yes", "-o", "DSAAuthentication=yes", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=\"#{ssh_info[:private_key_path][0]}\"")
end end
context "when disabling compression or dsa_authentication flags" do context "when disabling compression or dsa_authentication flags" do
@ -85,7 +85,7 @@ describe Vagrant::Util::SSH do
expect(described_class.exec(ssh_info)).to eq(nil) expect(described_class.exec(ssh_info)).to eq(nil)
expect(Vagrant::Util::SafeExec).to have_received(:exec) expect(Vagrant::Util::SafeExec).to have_received(:exec)
.with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=#{ssh_info[:private_key_path][0]}") .with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=\"#{ssh_info[:private_key_path][0]}\"")
end end
end end
@ -103,7 +103,7 @@ describe Vagrant::Util::SSH do
expect(described_class.exec(ssh_info)).to eq(nil) expect(described_class.exec(ssh_info)).to eq(nil)
expect(Vagrant::Util::SafeExec).to have_received(:exec) expect(Vagrant::Util::SafeExec).to have_received(:exec)
.with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "IdentityFile=#{ssh_info[:private_key_path][0]}") .with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "IdentityFile=\"#{ssh_info[:private_key_path][0]}\"")
end end
end end
@ -140,7 +140,7 @@ describe Vagrant::Util::SSH do
expect(described_class.exec(ssh_info)).to eq(nil) expect(described_class.exec(ssh_info)).to eq(nil)
expect(Vagrant::Util::SafeExec).to have_received(:exec) expect(Vagrant::Util::SafeExec).to have_received(:exec)
.with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=#{ssh_info[:private_key_path][0]}","-o", "ForwardX11=yes", "-o", "ForwardX11Trusted=yes") .with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=\"#{ssh_info[:private_key_path][0]}\"","-o", "ForwardX11=yes", "-o", "ForwardX11Trusted=yes")
end end
end end
@ -158,7 +158,7 @@ describe Vagrant::Util::SSH do
expect(described_class.exec(ssh_info)).to eq(nil) expect(described_class.exec(ssh_info)).to eq(nil)
expect(Vagrant::Util::SafeExec).to have_received(:exec) expect(Vagrant::Util::SafeExec).to have_received(:exec)
.with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=#{ssh_info[:private_key_path][0]}","-o", "ForwardAgent=yes") .with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=\"#{ssh_info[:private_key_path][0]}\"","-o", "ForwardAgent=yes")
end end
end end
@ -176,7 +176,7 @@ describe Vagrant::Util::SSH do
expect(described_class.exec(ssh_info)).to eq(nil) expect(described_class.exec(ssh_info)).to eq(nil)
expect(Vagrant::Util::SafeExec).to have_received(:exec) expect(Vagrant::Util::SafeExec).to have_received(:exec)
.with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=#{ssh_info[:private_key_path][0]}", "-L", "8008:localhost:80") .with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=\"#{ssh_info[:private_key_path][0]}\"", "-L", "8008:localhost:80")
end end
end end
@ -194,7 +194,7 @@ describe Vagrant::Util::SSH do
expect(described_class.exec(ssh_info)).to eq(nil) expect(described_class.exec(ssh_info)).to eq(nil)
expect(Vagrant::Util::SafeExec).to have_received(:exec) expect(Vagrant::Util::SafeExec).to have_received(:exec)
.with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=#{ssh_info[:private_key_path][0]}", "-6") .with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=\"#{ssh_info[:private_key_path][0]}\"", "-6")
end end
end end

View File

@ -1 +1 @@
1.9.9.dev 2.0.1.dev

View File

@ -2,7 +2,7 @@ set :base_url, "https://www.vagrantup.com/"
activate :hashicorp do |h| activate :hashicorp do |h|
h.name = "vagrant" h.name = "vagrant"
h.version = "1.9.8" h.version = "2.0.0"
h.github_slug = "mitchellh/vagrant" h.github_slug = "mitchellh/vagrant"
h.website_root = "website" h.website_root = "website"
end end

View File

@ -14,10 +14,8 @@ description: |-
The Vagrant Ansible provisioner allows you to provision the guest using [Ansible](http://ansible.com) playbooks by executing **`ansible-playbook` from the Vagrant host**. The Vagrant Ansible provisioner allows you to provision the guest using [Ansible](http://ansible.com) playbooks by executing **`ansible-playbook` from the Vagrant host**.
<div class="alert alert-warning"> <div class="alert alert-warning">
<strong>Warning:</strong> If you are not familiar with Ansible and Vagrant already, <strong>Warning:</strong>
I recommend starting with the <a href="/docs/provisioning/shell.html">shell If you are not familiar with Ansible and Vagrant already, I recommend starting with the <a href="/docs/provisioning/shell.html">shell provisioner</a>. However, if you are comfortable with Vagrant already, Vagrant is a great way to learn Ansible.
provisioner</a>. However, if you are comfortable with Vagrant already, Vagrant
is a great way to learn Ansible.
</div> </div>
## Setup Requirements ## Setup Requirements
@ -53,10 +51,17 @@ end
This section lists the _specific_ options for the Ansible (remote) provisioner. In addition to the options listed below, this provisioner supports the [**common options** for both Ansible provisioners](/docs/provisioning/ansible_common.html). This section lists the _specific_ options for the Ansible (remote) provisioner. In addition to the options listed below, this provisioner supports the [**common options** for both Ansible provisioners](/docs/provisioning/ansible_common.html).
- `ask_sudo_pass` (boolean) - require Ansible to [prompt for a sudo password](https://docs.ansible.com/intro_getting_started.html#remote-connection-information). - `ask_become_pass` (boolean) - require Ansible to [prompt for a password](https://docs.ansible.com/intro_getting_started.html#remote-connection-information) when switching to another user with the [become/sudo mechanism](http://docs.ansible.com/ansible/become.html).
The default value is `false`. The default value is `false`.
- `ask_sudo_pass` (boolean) - Backwards compatible alias for the [ask_become_pass](#ask_become_pass) option.
<div class="alert alert-warning">
<strong>Deprecation:</strong>
The `ask_sudo_pass` option is deprecated and will be removed in a future release. Please use the [**`ask_become_pass`**](#ask_become_pass) option instead.
</div>
- `ask_vault_pass` (boolean) - require Ansible to [prompt for a vault password](https://docs.ansible.com/playbooks_vault.html#vault). - `ask_vault_pass` (boolean) - require Ansible to [prompt for a vault password](https://docs.ansible.com/playbooks_vault.html#vault).
The default value is `false`. The default value is `false`.
@ -67,7 +72,10 @@ This section lists the _specific_ options for the Ansible (remote) provisioner.
The default value is `true`. The default value is `true`.
**Note:** This option was introduced in Vagrant 1.8.0. Previous Vagrant versions behave like if this option was set to `false`. <div class="alert alert-info">
<strong>Compatibility Note:</strong>
This option was introduced in Vagrant 1.8.0. Previous Vagrant versions behave like if this option was set to `false`.
</div>
- `host_key_checking` (boolean) - require Ansible to [enable SSH host key checking](https://docs.ansible.com/intro_getting_started.html#host-key-checking). - `host_key_checking` (boolean) - require Ansible to [enable SSH host key checking](https://docs.ansible.com/intro_getting_started.html#host-key-checking).
@ -115,9 +123,10 @@ N = 3
end end
``` ```
**Caveats:** <div class="alert alert-info">
<strong>Tip:</strong>
If you apply this parallel provisioning pattern with a static Ansible inventory, you will have to organize the things so that [all the relevant private keys are provided to the `ansible-playbook` command](https://github.com/mitchellh/vagrant/pull/5765#issuecomment-120247738). The same kind of considerations applies if you are using multiple private keys for a same machine (see [`config.ssh.private_key_path` SSH setting](/docs/vagrantfile/ssh_settings.html)). If you apply this parallel provisioning pattern with a static Ansible inventory, you will have to organize the things so that [all the relevant private keys are provided to the `ansible-playbook` command](https://github.com/mitchellh/vagrant/pull/5765#issuecomment-120247738). The same kind of considerations applies if you are using multiple private keys for a same machine (see [`config.ssh.private_key_path` SSH setting](/docs/vagrantfile/ssh_settings.html)).
</div>
### Force Paramiko Connection Mode ### Force Paramiko Connection Mode

View File

@ -17,6 +17,36 @@ These options get passed to the `ansible-playbook` command that ships with Ansib
Some of these options are for advanced usage only and should not be used unless you understand their purpose. Some of these options are for advanced usage only and should not be used unless you understand their purpose.
- `become` (boolean) - Perform all the Ansible playbook tasks [as another user](http://docs.ansible.com/ansible/become.html), different from the user used to log into the guest system.
The default value is `false`.
- `become_user` (string) - Set the default username to be used by the Ansible `become` [privilege escalation](http://docs.ansible.com/ansible/become.html) mechanism.
By default this option is not set, and the Ansible default value (`root`) will be used.
- `compatibility_mode` (string) - Set the **minimal** version of Ansible to be supported. Vagrant will only use parameters that are compatible with the given version.
Possible values:
- `"auto"` _(Vagrant will automatically select the optimal compatibilty mode by checking the Ansible version currently available)_
- `"1.8"` _(Ansible versions prior to 1.8 should mostly work well, but some options might not be supported)_
- `"2.0"` _(The generated Ansible inventory will be incompatible with Ansible 1.x)_
By default this option is set to `"auto"`. If Vagrant is not able to detect any supported Ansible version, it will fall back on the compatibility mode `"1.8"` with a warning.
Vagrant will error if the specified compatibility mode is incompatible with the current Ansible version.
<div class="alert alert-warning">
<strong>Attention:</strong>
Vagrant doesn't perform any validation between the `compatibility_mode` value and the value of the [`version`](#version) option.
</div>
<div class="alert alert-info">
<strong>Compatibility Note:</strong>
This option was introduced in Vagrant 2.0. The behavior of previous Vagrant versions can be simulated by setting the `compatibility_mode` to `"1.8"`.
</div>
- `config_file` (string) - The path to an [Ansible Configuration file](https://docs.ansible.com/intro_configuration.html). - `config_file` (string) - The path to an [Ansible Configuration file](https://docs.ansible.com/intro_configuration.html).
By default, this option is not set, and Ansible will [search for a possible configuration file in some default locations](/docs/provisioning/ansible_intro.html#ANSIBLE_CONFIG). By default, this option is not set, and Ansible will [search for a possible configuration file in some default locations](/docs/provisioning/ansible_intro.html#ANSIBLE_CONFIG).
@ -93,7 +123,7 @@ Some of these options are for advanced usage only and should not be used unless
ansible.host_vars = { ansible.host_vars = {
"host1" => {"http_port" => 80, "host1" => {"http_port" => 80,
"maxRequestsPerChild" => 808}, "maxRequestsPerChild" => 808},
"comments" => "'text with spaces'", "comments" => "text with spaces",
"host2" => {"http_port" => 303, "host2" => {"http_port" => 303,
"maxRequestsPerChild" => 909} "maxRequestsPerChild" => 909}
} }
@ -123,17 +153,28 @@ Some of these options are for advanced usage only and should not be used unless
- `['--check', '-M', '/my/modules']` - `['--check', '-M', '/my/modules']`
- `["--connection=paramiko", "--forks=10"]` - `["--connection=paramiko", "--forks=10"]`
**Caveat:** The `ansible` provisioner does not support whitespace characters in `raw_arguments` elements. Therefore **don't write** something like `["-c paramiko"]`, which will result with an invalid `" parmiko"` parameter value. <div class="alert alert-warn">
<strong>Attention:</strong>
The `ansible` provisioner does not support whitespace characters in `raw_arguments` elements. Therefore **don't write** something like `["-c paramiko"]`, which will result with an invalid `" parmiko"` parameter value.
</div>
- `skip_tags` (string or array of strings) - Only plays, roles and tasks that [*do not match* these values will be executed](https://docs.ansible.com/playbooks_tags.html). - `skip_tags` (string or array of strings) - Only plays, roles and tasks that [*do not match* these values will be executed](https://docs.ansible.com/playbooks_tags.html).
- `start_at_task` (string) - The task name where the [playbook execution will start](https://docs.ansible.com/playbooks_startnstep.html#start-at-task). - `start_at_task` (string) - The task name where the [playbook execution will start](https://docs.ansible.com/playbooks_startnstep.html#start-at-task).
- `sudo` (boolean) - Cause Ansible to perform all the playbook tasks [using sudo](https://docs.ansible.com/glossary.html#sudo). - `sudo` (boolean) - Backwards compatible alias for the [`become`](#become) option.
The default value is `false`. <div class="alert alert-warning">
<strong>Deprecation:</strong>
The `sudo` option is deprecated and will be removed in a future release. Please use the [**`become`**](#become) option instead.
</div>
- `sudo_user` (string) - set the default username who should be used by the sudo command. - `sudo_user` (string) - Backwards compatible alias for the [`become_user`](#become_user) option.
<div class="alert alert-warning">
<strong>Deprecation:</strong>
The `sudo_user` option is deprecated and will be removed in a future release. Please use the [**`become_user`**](#become_user) option instead.
</div>
- `tags` (string or array of strings) - Only plays, roles and tasks [tagged with these values will be executed](https://docs.ansible.com/playbooks_tags.html) . - `tags` (string or array of strings) - Only plays, roles and tasks [tagged with these values will be executed](https://docs.ansible.com/playbooks_tags.html) .
@ -146,3 +187,16 @@ Some of these options are for advanced usage only and should not be used unless
Examples: `true` (equivalent to `v`), `-vvv` (equivalent to `vvv`), `vvvv`. Examples: `true` (equivalent to `v`), `-vvv` (equivalent to `vvv`), `vvvv`.
Note that when the `verbose` option is enabled, the `ansible-playbook` command used by Vagrant will be displayed. Note that when the `verbose` option is enabled, the `ansible-playbook` command used by Vagrant will be displayed.
- `version` (string) - The expected Ansible version.
This option is disabled by default.
When an Ansible version is defined (e.g. `"2.1.6.0"`), the Ansible provisioner will be executed only if Ansible is installed at the requested version.
When this option is set to `"latest"`, no version check is applied.
<div class="alert alert-info">
<strong>Tip:</strong>
With the `ansible_local` provisioner, it is currently possible to use this option to specify which version of Ansible must be automatically installed, but <strong>only</strong> in combination with the [**`install_mode`**](/docs/provisioning/ansible_local.html#install_mode) set to <strong>`:pip`</strong>.
</div>

View File

@ -14,10 +14,8 @@ description: |-
The Vagrant Ansible Local provisioner allows you to provision the guest using [Ansible](http://ansible.com) playbooks by executing **`ansible-playbook` directly on the guest machine**. The Vagrant Ansible Local provisioner allows you to provision the guest using [Ansible](http://ansible.com) playbooks by executing **`ansible-playbook` directly on the guest machine**.
<div class="alert alert-warning"> <div class="alert alert-warning">
<strong>Warning:</strong> If you are not familiar with Ansible and Vagrant already, <strong>Warning:</strong>
I recommend starting with the <a href="/docs/provisioning/shell.html">shell If you are not familiar with Ansible and Vagrant already, I recommend starting with the <a href="/docs/provisioning/shell.html">shell provisioner</a>. However, if you are comfortable with Vagrant already, Vagrant is a great way to learn Ansible.
provisioner</a>. However, if you are comfortable with Vagrant already, Vagrant
is a great way to learn Ansible.
</div> </div>
## Setup Requirements ## Setup Requirements
@ -64,10 +62,13 @@ This section lists the _specific_ options for the Ansible Local provisioner. In
Vagrant will try to install (or upgrade) Ansible when one of these conditions are met: Vagrant will try to install (or upgrade) Ansible when one of these conditions are met:
- Ansible is not installed (or cannot be found). - Ansible is not installed (or cannot be found).
- The `version` option is set to `"latest"`. - The [`version`](/docs/provisioning/ansible_common.html#version) option is set to `"latest"`.
- The current Ansible version does not correspond to the `version` option. - The current Ansible version does not correspond to the [`version`](/docs/provisioning/ansible_common.html#version) option.
**Attention:** There is no guarantee that this automated installation will replace a custom Ansible setup, that might be already present on the Vagrant box. <div class="alert alert-warning">
<strong>Attention:</strong>
There is no guarantee that this automated installation will replace a custom Ansible setup, that might be already present on the Vagrant box.
</div>
- `install_mode` (`:default`, `:pip`, or `:pip_args_only`) - Select the way to automatically install Ansible on the guest system. - `install_mode` (`:default`, `:pip`, or `:pip_args_only`) - Select the way to automatically install Ansible on the guest system.
@ -75,7 +76,7 @@ This section lists the _specific_ options for the Ansible Local provisioner. In
- On Ubuntu-like systems, the latest Ansible release is installed from the `ppa:ansible/ansible` repository. - On Ubuntu-like systems, the latest Ansible release is installed from the `ppa:ansible/ansible` repository.
- On RedHat-like systems, the latest Ansible release is installed from the [EPEL](http://fedoraproject.org/wiki/EPEL) repository. - On RedHat-like systems, the latest Ansible release is installed from the [EPEL](http://fedoraproject.org/wiki/EPEL) repository.
- `:pip`: Ansible is installed from [PyPI](https://pypi.python.org/pypi) with [pip](https://pip.pypa.io) package installer. With this mode, Vagrant will systematically try to [install the latest pip version](https://pip.pypa.io/en/stable/installing/#installing-with-get-pip-py). With the `:pip` mode you can optionally install a specific Ansible release by setting the [`version`](#version) option. - `:pip`: Ansible is installed from [PyPI](https://pypi.python.org/pypi) with [pip](https://pip.pypa.io) package installer. With this mode, Vagrant will systematically try to [install the latest pip version](https://pip.pypa.io/en/stable/installing/#installing-with-get-pip-py). With the `:pip` mode you can optionally install a specific Ansible release by setting the [`version`](/docs/provisioning/ansible_common.html#version) option.
Example: Example:
@ -140,16 +141,6 @@ This section lists the _specific_ options for the Ansible Local provisioner. In
The default value is `/tmp/vagrant-ansible` The default value is `/tmp/vagrant-ansible`
- `version` (string) - The expected Ansible version.
This option is disabled by default.
When an Ansible version is defined (e.g. `"1.8.2"`), the Ansible local provisioner will be executed only if Ansible is installed at the requested version.
When this option is set to `"latest"`, no version check is applied.
**Warning:** It is currently possible to use this option to specify which version of Ansible must be automatically installed, but only in combination with the `install_mode` set to `:pip`.
## Tips and Tricks ## Tips and Tricks
### Ansible Parallel Execution from a Guest ### Ansible Parallel Execution from a Guest

View File

@ -25,6 +25,42 @@ new VM.
config.vm.provision "file", source: "~/.gitconfig", destination: ".gitconfig" config.vm.provision "file", source: "~/.gitconfig", destination: ".gitconfig"
end end
If you want to upload a folder to your guest system, it can be accomplished by
using a file provisioner seen below. When copied, the resulting folder on the guest will
replace `folder` as `newfolder` and place its on the guest machine. Note that if
you'd like the same folder name on your guest machine, make sure that the destination
path has the same name as the folder on your host.
Vagrant.configure("2") do |config|
# ... other configuration
config.vm.provision "file", source: "~/path/to/host/folder", destination: "$HOME/remote/newfolder"
end
Prior to copying `~/path/to/host/folder` to the guest machine:
folder
├── script.sh
├── otherfolder
│   └── hello.sh
├── goodbye.sh
├── hello.sh
└── woot.sh
1 directory, 5 files
After to copying `~/path/to/host/folder` into `$HOME/remote/newfolder` to the guest machine:
newfolder
├── script.sh
├── otherfolder
│   └── hello.sh
├── goodbye.sh
├── hello.sh
└── woot.sh
1 directory, 5 files
Note that, unlike with synced folders, files or directories that are uploaded Note that, unlike with synced folders, files or directories that are uploaded
will not be kept in sync. Continuing with the example above, if you make will not be kept in sync. Continuing with the example above, if you make
further changes to your local ~/.gitconfig, they will not be immediately further changes to your local ~/.gitconfig, they will not be immediately
@ -49,3 +85,36 @@ The file provisioner takes only two options, both of which are required:
the source will be uploaded to. The file/folder is uploaded as the SSH user the source will be uploaded to. The file/folder is uploaded as the SSH user
over SCP, so this location must be writable to that user. The SSH user can be over SCP, so this location must be writable to that user. The SSH user can be
determined by running `vagrant ssh-config`, and defaults to "vagrant". determined by running `vagrant ssh-config`, and defaults to "vagrant".
## Caveats
While the file provisioner does support trailing slashes or "globing", this can
lead to some confusing results due to the underlying tool used to copy files and
folders between the host and guests. For example, if you have a source and
destination with a trailing slash defined below:
config.vm.provision "file", source: "~/pathfolder", destination: "/remote/newlocation/"
You are telling vagrant to upload `~/pathfolder` under the remote dir `/remote/newlocation`,
which will look like:
newlocation
├── pathfolder
│   └── file.sh
1 directory, 2 files
This behavior can also be achieved by defining your file provisioner below:
config.vm.provision "file", source: "~/pathfolder", destination: "/remote/newlocation/pathfolder"
Another example is using globing on the host machine to grab all files within a
folder, but not the top level folder itself:
config.vm.provision "file", source: "~/otherfolder/.", destination: "/remote/otherlocation"
The file provisioner is defined to include all files under `~/otherfolder`
to the new location `/remote/otherlocation`. This idea can be achieved by simply
having your destination folder differ from the source folder:
config.vm.provision "file", source: "/otherfolder", destination: "/remote/otherlocation"

View File

@ -96,6 +96,8 @@ public key
* `masterless` (boolean) - Calls state.highstate in local mode. Uses `minion_id` and `pillar_data` when provided. * `masterless` (boolean) - Calls state.highstate in local mode. Uses `minion_id` and `pillar_data` when provided.
* `salt_call_args` (array) - An array of additional command line flag arguments to be passed to the `salt-call` command when provisioning with masterless.
## Master Options ## Master Options
These only make sense when `install_master` is `true`. Not supported on Windows guest machines. These only make sense when `install_master` is `true`. Not supported on Windows guest machines.
@ -109,6 +111,8 @@ These only make sense when `install_master` is `true`. Not supported on Windows
* `seed_master` (dictionary) - Upload keys to master, thereby * `seed_master` (dictionary) - Upload keys to master, thereby
pre-seeding it before use. Example: `{minion_name:/path/to/key.pub}` pre-seeding it before use. Example: `{minion_name:/path/to/key.pub}`
* `salt_args` (array) - An array of additional command line flag arguments to be passed to the `salt` command when provisioning with masterless.
## Execute States ## Execute States
Either of the following may be used to actually execute states Either of the following may be used to actually execute states