Merge branch 'master' into VB-linked-clone-support

This commit is contained in:
Manuel Pöter 2015-07-13 10:56:17 +02:00
commit f4d1d068f9
492 changed files with 16418 additions and 2441 deletions

3
.gitignore vendored
View File

@ -16,6 +16,7 @@ pkg/*
tags
/Gemfile.lock
test/tmp/
vendor/
# Documentation
_site/*
@ -31,6 +32,7 @@ doc/
# IDE junk
.idea/*
*.iml
.project
# Ruby Managers
.rbenv
@ -47,3 +49,4 @@ website/docs/Rakefile
website/www/.sass-cache
website/www/build
website/www/Rakefile
exec/

View File

@ -1,10 +1,23 @@
language: ruby
before_install:
- sudo apt-get update -qq
- sudo apt-get install -qq -y bsdtar
sudo: false
cache: bundler
addons:
apt:
packages:
- bsdtar
rvm:
- 2.0.0
branches:
only:
- master
env:
global:
- NOKOGIRI_USE_SYSTEM_LIBRARIES=true
script: rake test:unit
script: bundle exec rake test:unit

View File

@ -1,3 +1,334 @@
## 1.7.4 (unreleased)
## 1.7.3 (July 10, 2015)
FEATURES:
- **New guest: `atomic`* - Project Atomic is supported as a guest
- providers/virtualbox: add support for 5.0 [GH-5647]
IMPROVEMENTS:
- core: add password authentication to rdp_info hash [GH-4726]
- core: improve error message when packaging fails [GH-5399]
- core: improve message when adding a box from a file path [GH-5395]
- core: add support for network gateways [GH-5721]
- core: allow redirecting stdout and stderr in the UI [GH-5433]
- core: update version of winrm-fs to 0.2.0 [GH-5738]
- core: add option to enabled trusted http(s) redirects [GH-4422]
- core: capture additional information such as line numbers during
Vagrantfile loading [GH-4711, GH-5769]
- core: add .color? to UI objects to see if they support color [GH-5771]
- core: ignore hidden directories when searching for boxes [GH-5748, GH-5785]
- core: use `config.ssh.sudo_command` to customize the sudo command
format [GH-5573]
- core: add `Vagrant.original_env` for Vagrant and plugins to restore or
inspect the original environment when Vagrant is being run from the
installer [GH-5910]
- guests/darwin: support inserting generated key [GH-5204]
- guests/darwin: support mounting SMB shares [GH-5750]
- guests/fedora: support Fedora 21 [GH-5277]
- guests/fedora: add capabilities for nfs and flavor [GH-5770, GH-4847]
- guests/linux: specify user's domain as separate parameter [GH-3620, GH-5512]
- guests/redhat: support Scientific Linux 7 [GH-5303]
- guests/photon: initial support [GH-5612]
- guests/solaris,solaris11: support inserting generated key [GH-5182]
[GH-5290]
- providers/docker: images are pulled prior to starting [GH-5249]
- provisioners/ansible: store the first ssh private key in the auto-generated inventory [GH-5765]
- provisioners/chef: add capability for checking if Chef is installed on Windows [GH-5669]
- provisioners/docker: restart containers if arguments have changed [GH-3055, GH-5924]
- provisioners/puppet: add support for Puppet 4 and configuration options [GH-5601]
- provisioners/puppet: add support for `synced_folder_args` in apply [GH-5359]
- provisioners/salt: add configurable `config_dir` [GH-3138]
- provisioners/salt: add support for masterless configuration [GH-3235]
- provisioners/salt: provider path to missing file in errors [GH-5637]
- provisioners/salt: add ability to run salt orchestrations [GH-4371]
- provisioners/salt: update to 2015.5.2 [GH-4152, GH-5437]
- provisioners/salt: support specifying version to install [GH-5892]
- provisioners/shell: add :name attribute to shell provisioner [GH-5607]
- providers/docker: supports file downloads with the file provisioner [GH-5651]
- providers/docker: support named Dockerfile [GH-5480]
- providers/docker: don't remove image on reload so that build cache can
be used fully [GH-5905]
- providers/hyperv: select a Hyper-V switch based on a `network_name` [GH-5207]
- providers/hyperv: allow configuring VladID [GH-5539]
- providers/virtualbox: regexp supported for bridge configuration [GH-5320]
- providers/virtualbox: handle a list of bridged NICs [GH-5691]
- synced_folders/rsync: allow showing rsync output in debug mode [GH-4867]
- synced_folders/rsync: set `rsync__rsync_path` to specify the remote
command used to execute rsync [GH-3966]
BUG FIXES:
- core: push configurations are validated with global configs [GH-5130]
- core: remove executable permissions on internal file [GH-5220]
- core: check name and version in `has_plugin?` [GH-5218]
- core: do not create duplicates when defining two private network addresses [GH-5325]
- core: update ssh to check for Plink [GH-5604]
- core: do not report plugins as installed when plugins are disabled [GH-5698, GH-5430]
- core: Only take files when packaging a box to avoid duplicates [GH-5658, GH-5657]
- core: escape curl urls and authentication [GH-5677]
- core: fix crash if a value is missing for CLI arguments [GH-5550]
- core: retry SSH key generation for transient RSA errors [GH-5056]
- core: `ssh.private_key_path` will override the insecure key [GH-5632]
- core: restore the original environment when shelling out to subprocesses
outside of the installer [GH-5912]
- core/cli: fix box checksum validation [GH-4665, GH-5221]
- core/windows: allow Windows UNC paths to allow more than 256
characters [GH-4815]
- command/rsync-auto: don't crash if rsync command fails [GH-4991]
- communicators/winrm: improve error handling significantly and improve
the error messages shown to be more human-friendly. [GH-4943]
- communicators/winrm: remove plaintext passwords from files after
provisioner is complete [GH-5818]
- hosts/nfs: allow colons (`:`) in NFS IDs [GH-5222]
- guests/darwin: remove dots from LocalHostName [GH-5558]
- guests/debian: Halt works properly on Debian 8. [GH-5369]
- guests/fedora: recognize future fedora releases [GH-5730]
- guests/fedora: reload iface connection by NetworkManager [GH-5709]
- guests/fedora: do not use biosdevname if it is not installed [GH-5707]
- guests/freebsd: provide an argument to the backup file [GH-5516, GH-5517]
- guests/funtoo: fix incorrect path in configure networks [GH-4812]
- guests/linux: fix edge case exception where no home directory
is available on guest [GH-5846]
- guests/linux: copy NFS exports to tmpdir to do edits to guarantee
permissions are available [GH-5773]
- guests/openbsd: output newline after inserted public key [GH-5881]
- guests/tinycore: fix change hostname functionality [GH-5623]
- guests/ubuntu: use `hostnamectl` to set hostname on Ubuntu Vivid [GH-5753]
- guests/windows: Create rsync folder prior to rsync-ing. [GH-5282]
- guests/windows: Changing hostname requires reboot again since
the non-reboot code path was crashing Windows server. [GH-5261]
- guests/windows: ignore virtual NICs [GH-5478]
- hosts/windows: More accurately get host IP address in VPNs. [GH-5349]
- plugins/login: allow users to login with a token [GH-5145]
- providers/docker: Build image from `/var/lib/docker` for more disk
space on some systems. [GH-5302]
- providers/docker: Fix crash that could occur in some scenarios when
the host VM path changed.
- providers/docker: Fix crash that could occur on container destroy
with VirtualBox shared folders [GH-5143]
- providers/hyperv: allow users to configure memory, cpu count, and vmname [GH-5183]
- providers/hyperv: import respects secure boot. [GH-5209]
- providers/hyperv: only set EFI secure boot for gen 2 machines [GH-5538]
- providers/virtualbox: read netmask from dhcpservers [GH-5233]
- providers/virtualbox: Fix exception when VirtualBox version is empty. [GH-5308]
- providers/virtualbox: Fix exception when VBoxManage.exe can't be run
on Windows [GH-1483]
- providers/virtualbox: Error if another user is running after a VM is
created to avoid issue with VirtualBox "losing" the VM [GH-5895]
- providers/virtualbox: The "name" setting on private networks will
choose an existing hostonly network [GH-5389]
- provisioners/ansible: fix SSH settings to support more than 5 ssh keys [GH-5017]
- provisioners/ansible: increase ansible connection timeout to 30 seconds [GH-5018]
- provisioners/ansible: disable color if Vagrant is not colored [GH-5531, GH-5532]
- provisioners/ansible: only show ansible-playbook command when `verbose` option is enabled [GH-5803]
- provisioners/ansible: fix a race condition in the inventory file generation [GH-5551]
- provisioners/docker: use `service` to restart Docker instad of upstart [GH-5245, GH-5577]
- provisioners/docker: Only add docker user to group if exists. [GH-5315]
- provisioners/docker: Use https for repo [GH-5749]
- provisioners/docker: `apt-get update` before installing linux kernel
images to get the correct version [GH-5860]
- provisioners/chef: Fix shared folders missing error [GH-5199]
- provisioners/chef: Use `command -v` to check for binary instead of
`which` since that doesn't exist on some systems. [GH-5170]
- provisioners/chef-zero: support more chef-zero/local mode attributes [GH-5339]
- provisioners/chef: use windows-specific paths in Chef provisioners [GH-5913]
- provisioners/docker: use docker.com instead of docker.io [GH-5216]
- provisioners/docker: use `--restart` instead of `-r` on daemon [GH-4477]
- provisioners/file: validation of source is relative to Vagrantfile [GH-5252]
- pushes/atlas: send additional box metadata [GH-5283]
- pushes/local-exec: fix "text file busy" error for inline [GH-5695]
- pushes/ftp: improve check for remote directory existence [GH-5549]
- synced\_folders/rsync: add `IdentitiesOnly=yes` to the rsync command. [GH-5175]
- synced\_folders/smb: use correct `password` option [GH-5805]
- synced\_folders/smb: prever IPv4 over IPv6 address to mount [GH-5798]
- virtualbox/config: fix misleading error message for private_network [GH-5536, GH-5418]
## 1.7.2 (January 6, 2015)
BREAKING CHANGES:
- If you depended on the paths that Chef/Puppet provisioners use to
store cookbooks (ex. "/tmp/vagrant-chef-1"), these will no longer be
correct. Without this change, Chef/Puppet didn't work at all with
`vagrant provision`. We expect this to affect only a minor number of
people, since it's not something that was ever documented or recommended
by Vagrant, or even meant to be supported.
FEATURES:
- provisioners/salt: add support for grains [GH-4895]
IMPROVEMENTS:
- commands/reload,up: `--provision-with` implies `--provision` [GH-5085]
BUG FIXES:
- core: private boxes still referencing vagrantcloud.com will have
their vagrant login access token properly appended
- core: push plugin configuration is properly validated
- core: restore box packaging functionality
- commands/package: fix crash
- commands/push: push lookups are by user-defined name, not push
strategy name [GH-4975]
- commands/push: validate the configuration
- communicators/winrm: detect parse errors in PowerShell and error
- guests/arch: fix network configuration due to poor line breaks. [GH-4964]
- guests/solaris: Merge configurations properly so configs can be set
in default Vagrantfiles. [GH-5092]
- installer: SSL cert bundle contains 1024-bit keys, fixing SSL verification
for a lot of sites.
- installer: vagrant executable properly `cygpaths` the SSL bundle path
for Cygwin
- installer: Nokogiri (XML lib used by Vagrant and dependencies) linker
dependencies fixed, fixing load issues on some platforms
- providers/docker: Symlinks in shared folders work. [GH-5093]
- providers/hyperv: VM start errors turn into proper Vagrant errors. [GH-5101]
- provisioners/chef: fix missing shared folder error [GH-4988]
- provisioners/chef: remove Chef version check from solo.rb generation and
make `roles_path` populate correctly
- provisioners/chef: fix bad invocation of `with_clean_env` [GH-5021]
- pushes/atlas: support more verbose logging
- pushes/ftp: expand file paths relative to the Vagrantfile
- pushes/ftp: improved debugging output
- pushes/ftp: create parent directories if they do not exist on the remote
server
## 1.7.1 (December 11, 2014)
IMPROVEMENTS:
- provisioners/ansible: Use Docker proxy if needed. [GH-4906]
BUG FIXES:
- providers/docker: Add support of SSH agent forwarding. [GH-4905]
## 1.7.0 (December 9, 2014)
BREAKING CHANGES:
- provisioners/ansible: `raw_arguments` has now highest priority
- provisioners/ansible: only the `ssh` connection transport is supported
(`paramiko` can be enabled with `raw_arguments` at your own risks)
FEATURES:
- **Vagrant Push**: Vagrant can now deploy! `vagrant push` is a single
command to deploy your application. Deploy to Heroku, FTP, or
HashiCorp's commercial product Atlas. New push strategies can be
added with plugins.
- **Named provisioners**: Provisioners can now be named. This name is used
for output as well as `--provision-with` for better control.
- Default provider logic improved: Providers in `config.vm.provider` blocks
in your Vagrantfile now have higher priority than plugins. Earlier
providers are chosen before later ones. [GH-3812]
- If the default insecure keypair is used, Vagrant will automatically replace
it with a randomly generated keypair on first `vagrant up`. [GH-2608]
- Vagrant Login is now part of Vagrant core
- Chef Zero provisioner: Use Chef 11's "local" mode to run recipes against an
in-memory Chef Server
- Chef Apply provisioner: Specify inline Chef recipes and recipe snippets
using the Chef Apply provisioner
IMPROVEMENTS:
- core: `has_plugin?` function now takes a second argument which is a
version constraint requirement. [GH-4650]
- core: ".vagrantplugins" file in the same folder as your Vagrantfile
will be loaded for defining inline plugins. [GH-3775]
- commands/plugin: Plugin list machine-readable output contains the plugin
name as the target for versions and other info. [GH-4506]
- env/with_cleanenv: New helper for plugin developers to use when shelling out
to another Ruby environment
- guests/arch: Support predictable network interface naming. [GH-4468]
- guests/suse: Support NFS client install, rsync setup. [GH-4492]
- guests/tinycore: Support changing host names. [GH-4469]
- guests/tinycore: Support DHCP-based networks. [GH-4710]
- guests/windows: Hostname can be set without reboot. [GH-4687]
- providers/docker: Build output is now shown. [GH-3739]
- providers/docker: Can now start containers from private repositories
more easily. Vagrant will login for you if you specify auth. [GH-4042]
- providers/docker: `stop_timeout` can be used to modify the `docker stop`
timeout. [GH-4504]
- provisioners/chef: Automatically install Chef when using a Chef provisioner.
- provisioners/ansible: Always show Ansible command executed when Vagrant log
level is debug (even if ansible.verbose is false)
- synced\_folders/nfs: Won't use `sudo` to write to /etc/exports if there
are write privileges. [GH-2643]
- synced\_folders/smb: Credentials from one SMB will be copied to the rest. [GH-4675]
BUG FIXES:
- core: Fix cases where sometimes SSH connection would hang.
- core: On a graceful halt, force halt if capability "insert public key"
is missing. [GH-4684]
- core: Don't share `/vagrant` if any "." folder is shared. [GH-4675]
- core: Fix SSH private key permissions more aggressively. [GH-4670]
- core: Custom Vagrant Cloud server URL now respected in more cases.
- core: On downloads, don't continue downloads if the remote server
doesn't support byte ranges. [GH-4479]
- core: Box downloads recognize more complex content types that include
"application/json" [GH-4525]
- core: If all sub-machines are `autostart: false`, don't start any. [GH-4552]
- core: Update global-status state in more cases. [GH-4513]
- core: Only delete machine state if the machine is not created in initialize
- commands/box: `--cert` flag works properly. [GH-4691]
- command/docker-logs: Won't crash if container is removed. [GH-3990]
- command/docker-run: Synced folders will be attached properly. [GH-3873]
- command/rsync: Sync to Docker containers properly. [GH-4066]
- guests/darwin: Hostname sets bonjour name and local host name. [GH-4535]
- guests/freebsd: NFS mounting can specify the version. [GH-4518]
- guests/linux: More descriptive error message if SMB mount fails. [GH-4641]
- guests/rhel: Hostname setting on 7.x series works properly. [GH-4527]
- guests/rhel: Installing NFS client works properly on 7.x [GH-4499]
- guests/solaris11: Static IP address preserved after restart. [GH-4621]
- guests/ubuntu: Detect with `lsb_release` instead of `/etc/issue`. [GH-4565]
- hosts/windows: RDP client shouldn't map all drives by default. [GH-4534]
- providers/docker: Create args works. [GH-4526]
- providers/docker: Nicer error if package is called. [GH-4595]
- providers/docker: Host IP restriction is forwarded through. [GH-4505]
- providers/docker: Protocol is now honored in direct `ports settings.
- providers/docker: Images built using `build_dir` will more robustly
capture the final image. [GH-4598]
- providers/docker: NFS synced folders now work. [GH-4344]
- providers/docker: Read the created container ID more robustly.
- providers/docker: `vagrant share` uses correct IP of proxy VM if it
exists. [GH-4342]
- providers/docker: `vagrant_vagrantfile` expands home directory. [GH-4000]
- providers/docker: Fix issue where multiple identical proxy VMs would
be created. [GH-3963]
- providers/docker: Multiple links with the same name work. [GH-4571]
- providers/virtualbox: Show a human-friendly error if VirtualBox didn't
clean up an existing VM. [GH-4681]
- providers/virtualbox: Detect case when VirtualBox reports 0.0.0.0 as
IP address and don't allow it. [GH-4671]
- providers/virtualbox: Show more descriptive error if VirtualBox is
reporting an empty version. [GH-4657]
- provisioners/ansible: Force `ssh` (OpenSSH) connection by default [GH-3396]
- provisioners/ansible: Don't use or modify `~/.ssh/known_hosts` file by default,
similarly to native vagrant commands [GH-3900]
- provisioners/ansible: Use intermediate Docker host when needed. [GH-4071]
- provisioners/docker: Get GPG key over SSL. [GH-4597]
- provisioners/docker: Search for docker binary in multiple places. [GH-4580]
- provisioners/salt: Highstate works properly with a master. [GH-4471]
- provisioners/shell: Retry getting SSH info a few times. [GH-3924]
- provisioners/shell: PowerShell scripts can have args. [GH-4548]
- synced\_folders/nfs: Don't modify NFS exports file if no exports. [GH-4619]
- synced\_folders/nfs: Prune exports for file path IDs. [GH-3815]
PLUGIN AUTHOR CHANGES:
- `Machine#action` can be called with the option `lock: false` to not
acquire a machine lock.
- `Machine#reload` will now properly trigger the `machine_id_changed`
callback on providers.
## 1.6.5 (September 4, 2014)
BUG FIXES:
@ -8,6 +339,8 @@ BUG FIXES:
- guests/redhat: Fix typo causing crash in configuring networks. [GH-4438]
- guests/redhat: Fix typo causing hostnames to not set. [GH-4443]
- providers/virtualbox: NFS works when using DHCP private network. [GH-4433]
- provisioners/salt: Fix error when removing non-existent bootstrap script
on Windows. [GH-4614]
## 1.6.4 (September 2, 2014)

View File

@ -7,7 +7,7 @@ The following guidelines for contribution should be followed if you want to subm
## How to prepare
* You need a [GitHub account](https://github.com/signup/free)
* Submit an [issue ticket](https://github.com/mitchellh/vagrant/issues) for your issue if the is no one yet.
* Submit an [issue ticket](https://github.com/mitchellh/vagrant/issues) for your issue if there is not one yet.
* Describe the issue and include steps to reproduce when it's a bug.
* Ensure to mention the earliest version that you know is affected.
* If you plan on submitting a bug report, please submit debug-level logs along

View File

@ -1,4 +1,4 @@
source "http://rubygems.org"
source "https://rubygems.org"
gemspec

View File

@ -1,6 +1,6 @@
The MIT License
Copyright (c) 2010-2014 Mitchell Hashimoto
Copyright (c) 2010-2015 Mitchell Hashimoto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -55,7 +55,7 @@ Ruby 2.0 is needed.
### Dependencies and Unit Tests
To hack on vagrant, you'll need [bundler](http://github.com/carlhuda/bundler) which can
To hack on Vagrant, you'll need [bundler](http://github.com/carlhuda/bundler) which can
be installed with a simple `gem install bundler`. Afterwards, do the following:
bundle install
@ -68,7 +68,7 @@ like so:
bundle exec vagrant help
**NOTE:** By default running Vagrant in via `bundle` will disable plugins.
**NOTE:** By default running Vagrant via `bundle` will disable plugins.
This is necessary because Vagrant creates its own private Bundler context
(it does not respect your Gemfile), because it uses Bundler to manage plugin
dependencies.

18
Vagrantfile vendored
View File

@ -5,17 +5,28 @@
Vagrant.configure("2") do |config|
config.vm.box = "hashicorp/precise64"
config.vm.hostname = "vagrant"
config.ssh.shell = "bash -c 'BASH_ENV=/etc/profile exec bash'"
["virtualbox", "vmware_fusion", "vmware_workstation"].each do |provider|
["vmware_fusion", "vmware_workstation", "virtualbox"].each do |provider|
config.vm.provider provider do |v, override|
v.memory = "1024"
end
end
config.vm.provision "shell", inline: $shell
config.push.define "www", strategy: "local-exec" do |push|
push.script = "scripts/website_push_www.sh"
end
config.push.define "docs", strategy: "local-exec" do |push|
push.script = "scripts/website_push_docs.sh"
end
end
$shell = <<-CONTENTS
export DEBIAN_FRONTEND=noninteractive
MARKER_FILE="/usr/local/etc/vagrant_provision_marker"
# Only provision once
@ -29,8 +40,11 @@ apt-get update
# Install basic dependencies
apt-get install -y build-essential bsdtar curl
# Import the mpapis public key to verify downloaded releases
su -l -c 'curl -sSL https://rvm.io/mpapis.asc | gpg -q --import -' vagrant
# Install RVM
su -l -c 'curl -L https://get.rvm.io | bash -s stable' vagrant
su -l -c 'curl -sL https://get.rvm.io | bash -s stable' vagrant
# Add the vagrant user to the RVM group
#usermod -a -G rvm vagrant

View File

@ -15,7 +15,7 @@ end
# Fast path the version of Vagrant
if argv.include?("-v") || argv.include?("--version")
require "vagrant/version"
require_relative "../lib/vagrant/version"
puts "Vagrant #{Vagrant::VERSION}"
exit 0
end
@ -68,7 +68,7 @@ end
# then also initialize the paths to the plugins.
require "bundler"
begin
Bundler.setup(:default, :plugins)
$vagrant_bundler_runtime = Bundler.setup(:default, :plugins)
rescue Bundler::GemNotFound
$stderr.puts "Bundler, the underlying system used to manage Vagrant plugins,"
$stderr.puts "is reporting that a plugin or its dependency can't be found."

View File

@ -65,13 +65,18 @@ _vagrant() {
then
case "$prev" in
"init")
local box_list=$(find $HOME/.vagrant.d/boxes -mindepth 1 -maxdepth 1 -type d -exec basename {} \;)
local box_list=$(find "${VAGRANT_HOME:-${HOME}/.vagrant.d}/boxes" -mindepth 1 -maxdepth 1 -type d -exec basename {} \;)
COMPREPLY=($(compgen -W "${box_list}" -- ${cur}))
return 0
;;
"up")
vagrant_state_file=$(__vagrantinvestigate) || return 1
if [[ -d $vagrant_state_file ]]
then
local vm_list=$(find $vagrant_state_file/machines -mindepth 1 -maxdepth 1 -type d -exec basename {} \;)
fi
local up_commands="--no-provision"
COMPREPLY=($(compgen -W "${up_commands}" -- ${cur}))
COMPREPLY=($(compgen -W "${up_commands} ${vm_list}" -- ${cur}))
return 0
;;
"ssh"|"provision"|"reload"|"halt"|"suspend"|"resume"|"ssh-config")
@ -107,18 +112,32 @@ _vagrant() {
if [ $COMP_CWORD == 3 ]
then
action="${COMP_WORDS[COMP_CWORD-2]}"
if [ $action == 'box' ]
then
case "$prev" in
case "$action" in
"up")
if [ "$prev" == "--no-provision" ]
then
if [[ -d $vagrant_state_file ]]
then
local vm_list=$(find $vagrant_state_file/machines -mindepth 1 -maxdepth 1 -type d -exec basename {} \;)
fi
COMPREPLY=($(compgen -W "${vm_list}" -- ${cur}))
return 0
fi
;;
"box")
case "$prev" in
"remove"|"repackage")
local box_list=$(find $HOME/.vagrant.d/boxes -mindepth 1 -maxdepth 1 -type d -exec basename {} \;)
local box_list=$(find "${VAGRANT_HOME:-${HOME}/.vagrant.d}/boxes" -mindepth 1 -maxdepth 1 -type d -exec basename {} \;)
COMPREPLY=($(compgen -W "${box_list}" -- ${cur}))
return 0
;;
*)
;;
esac
fi
;;
esac
;;
*)
;;
esac
fi
}

View File

@ -0,0 +1,6 @@
Cmnd_Alias VAGRANT_EXPORTS_ADD = /usr/bin/tee -a /etc/exports
Cmnd_Alias VAGRANT_NFSD_CHECK = /usr/bin/systemctl status nfs-server.service
Cmnd_Alias VAGRANT_NFSD_START = /usr/bin/systemctl start nfs-server.service
Cmnd_Alias VAGRANT_NFSD_APPLY = /usr/sbin/exportfs -ar
Cmnd_Alias VAGRANT_EXPORTS_REMOVE = /bin/sed -r -e * d -ibak /etc/exports
%vagrant ALL=(root) NOPASSWD: VAGRANT_EXPORTS_ADD, VAGRANT_NFSD_CHECK, VAGRANT_NFSD_START, VAGRANT_NFSD_APPLY, VAGRANT_EXPORTS_REMOVE

View File

@ -123,6 +123,7 @@ module Vagrant
c.register([:"2", :host]) { Plugin::V2::Host }
c.register([:"2", :provider]) { Plugin::V2::Provider }
c.register([:"2", :provisioner]) { Plugin::V2::Provisioner }
c.register([:"2", :push]) { Plugin::V2::Push }
c.register([:"2", :synced_folder]) { Plugin::V2::SyncedFolder }
end
@ -138,16 +139,27 @@ module Vagrant
Config.run(version, &block)
end
# This checks if a plugin with the given name is installed. This can
# be used from the Vagrantfile to easily branch based on plugin
# availability.
def self.has_plugin?(name)
# We check the plugin names first because those are cheaper to check
return true if plugin("2").manager.registered.any? { |p| p.name == name }
# This checks if a plugin with the given name is available (installed
# and enabled). This can be used from the Vagrantfile to easily branch
# based on plugin availability.
def self.has_plugin?(name, version=nil)
return false unless Vagrant.plugins_enabled?
if !version
# We check the plugin names first because those are cheaper to check
return true if plugin("2").manager.registered.any? { |p| p.name == name }
end
# Make the requirement object
version = Gem::Requirement.new([version]) if version
# Now check the plugin gem names
require "vagrant/plugin/manager"
Plugin::Manager.instance.installed_specs.any? { |s| s.name == name }
Plugin::Manager.instance.installed_specs.any? do |s|
match = s.name == name
next match if !version
next match && version.satisfied_by?(s.version)
end
end
# Returns a superclass to use when creating a plugin for Vagrant.
@ -215,6 +227,22 @@ module Vagrant
requirements: requirements.join(", "),
version: VERSION
end
# This allows plugin developers to access the original environment before
# Vagrant even ran. This is useful when shelling out, especially to other
# Ruby processes.
#
# @return [Hash]
def self.original_env
{}.tap do |h|
ENV.each do |k,v|
if k.start_with?("VAGRANT_OLD_ENV")
key = k.sub(/^VAGRANT_OLD_ENV_/, "")
h[key] = v
end
end
end
end
end
# Default I18n to load the en locale
@ -260,7 +288,7 @@ end
if Vagrant.plugins_enabled?
begin
global_logger.info("Loading plugins!")
Bundler.require(:plugins)
$vagrant_bundler_runtime.require(:plugins)
rescue Exception => e
raise Vagrant::Errors::PluginLoadError, message: e.to_s
end

View File

@ -115,6 +115,8 @@ module Vagrant
# @param [Array<String>] urls
# @param [Hash] env
def add_direct(urls, env)
env[:ui].output(I18n.t("vagrant.box_adding_direct"))
name = env[:box_name]
if !name || name == ""
raise Errors::BoxAddNameRequired
@ -147,7 +149,7 @@ module Vagrant
# element is an authenticated URL.
# @param [Hash] env
# @param [Bool] expanded True if the metadata URL was expanded with
# a Vagrant Cloud server URL.
# a Atlas server URL.
def add_from_metadata(url, env, expanded)
original_url = env[:box_url]
provider = env[:box_provider]
@ -174,6 +176,7 @@ module Vagrant
begin
metadata_path = download(
authenticated_url, env, json: true, ui: false)
return if @download_interrupted
File.open(metadata_path) do |f|
metadata = BoxMetadata.new(f)
@ -369,7 +372,7 @@ module Vagrant
#
# @return [Hash]
def downloader(url, env, **opts)
opts[:ui] = true if !opts.has_key?(:ui)
opts[:ui] = true if !opts.key?(:ui)
temp_path = env[:tmp_path].join("box" + Digest::SHA1.hexdigest(url))
@logger.info("Downloading box: #{url} => #{temp_path}")
@ -401,15 +404,16 @@ module Vagrant
downloader_options[:ca_path] = env[:box_download_ca_path]
downloader_options[:continue] = true
downloader_options[:insecure] = env[:box_download_insecure]
downloader_options[:client_cert] = env[:box_client_cert]
downloader_options[:client_cert] = env[:box_download_client_cert]
downloader_options[:headers] = ["Accept: application/json"] if opts[:json]
downloader_options[:ui] = env[:ui] if opts[:ui]
downloader_options[:location_trusted] = env[:box_download_location_trusted]
Util::Downloader.new(url, temp_path, downloader_options)
end
def download(url, env, **opts)
opts[:ui] = true if !opts.has_key?(:ui)
opts[:ui] = true if !opts.key?(:ui)
d = downloader(url, env, **opts)
@ -420,8 +424,15 @@ module Vagrant
show_url = opts[:show_url]
show_url ||= url
translation = "vagrant.box_downloading"
# Adjust status message when 'downloading' a local box.
if show_url.start_with?("file://")
translation = "vagrant.box_unpacking"
end
env[:ui].detail(I18n.t(
"vagrant.box_downloading",
translation,
url: show_url))
if File.file?(d.destination)
env[:ui].info(I18n.t("vagrant.actions.box.download.resuming"))
@ -483,7 +494,7 @@ module Vagrant
output = d.head
match = output.scan(/^Content-Type: (.+?)$/i).last
return false if !match
match.last.chomp == "application/json"
!!(match.last.chomp =~ /application\/json/)
end
def validate_checksum(checksum_type, checksum, path)

View File

@ -11,7 +11,7 @@ module Vagrant
end
def call(env)
if !env.has_key?(:config_validate) || env[:config_validate]
if !env.key?(:config_validate) || env[:config_validate]
errors = env[:machine].config.validate(env[:machine])
if errors && !errors.empty?

View File

@ -24,7 +24,7 @@ module Vagrant
def call(env)
graceful = true
graceful = !env[:force_halt] if env.has_key?(:force_halt)
graceful = !env[:force_halt] if env.key?(:force_halt)
# By default, we didn't succeed.
env[:result] = false
@ -59,6 +59,9 @@ module Vagrant
rescue Timeout::Error
# Don't worry about it, we catch the case later.
end
rescue Errors::GuestCapabilityNotFound
# This happens if insert_public_key is called on a guest that
# doesn't support it. This will block a destroy so we let it go.
rescue Errors::MachineGuestNotReady
env[:ui].detail(I18n.t("vagrant.actions.vm.halt.guest_not_ready"))
end

View File

@ -64,6 +64,9 @@ module Vagrant
box_download_ca_path = machine.config.vm.box_download_ca_path
box_download_client_cert = machine.config.vm.box_download_client_cert
box_download_insecure = machine.config.vm.box_download_insecure
box_download_checksum_type = machine.config.vm.box_download_checksum_type
box_download_checksum = machine.config.vm.box_download_checksum
box_download_location_trusted = machine.config.vm.box_download_location_trusted
box_formats = machine.provider_options[:box_format] ||
machine.provider_name
@ -79,12 +82,16 @@ module Vagrant
env[:action_runner].run(Vagrant::Action.action_box_add, env.merge({
box_name: machine.config.vm.box,
box_url: machine.config.vm.box_url || machine.config.vm.box,
box_server_url: machine.config.vm.box_server_url,
box_provider: box_formats,
box_version: machine.config.vm.box_version,
box_client_cert: box_download_client_cert,
box_download_client_cert: box_download_client_cert,
box_download_ca_cert: box_download_ca_cert,
box_download_ca_path: box_download_ca_path,
box_download_insecure: box_download_insecure,
box_checksum_type: box_download_checksum_type,
box_checksum: box_download_checksum,
box_download_location_trusted: box_download_location_trusted,
}))
rescue Errors::BoxAlreadyExists
# Just ignore this, since it means the next part will succeed!

View File

@ -15,7 +15,7 @@ module Vagrant
# Get all the configured provisioners
@_provisioner_instances = env[:machine].config.vm.provisioners.map do |provisioner|
# Instantiate the provisioner
klass = Vagrant.plugin("2").manager.provisioners[provisioner.name]
klass = Vagrant.plugin("2").manager.provisioners[provisioner.type]
# This can happen in the case the configuration isn't validated.
next nil if !klass
@ -23,11 +23,12 @@ module Vagrant
result = klass.new(env[:machine], provisioner.config)
# Store in the type map so that --provision-with works properly
@_provisioner_types[result] = provisioner.name
@_provisioner_types[result] = provisioner.type
# Build up the options
options = {
run: provisioner.run,
name: provisioner.name,
run: provisioner.run,
}
# Return the result

View File

@ -25,6 +25,15 @@ module Vagrant
# Order the plugins by priority. Higher is tried before lower.
ordered = ordered.sort { |a, b| b[0] <=> a[0] }
allowed_types = machine.config.vm.allowed_synced_folder_types
if allowed_types
ordered = allowed_types.map do |type|
ordered.find do |_, key, impl|
key == type
end
end.compact
end
# Find the proper implementation
ordered.each do |_, key, impl|
return key if impl.new.usable?(machine)
@ -93,11 +102,12 @@ module Vagrant
config = opts[:config]
config ||= machine.config.vm
config_folders = config.synced_folders
folders = {}
# Determine all the synced folders as well as the implementation
# they're going to use.
config.synced_folders.each do |id, data|
config_folders.each do |id, data|
# Ignore disabled synced folders
next if data[:disabled]
@ -112,10 +122,12 @@ module Vagrant
raise "Internal error. Report this as a bug. Invalid: #{data[:type]}"
end
if !impl_class[0].new.usable?(machine, true)
# Verify that explicitly defined shared folder types are
# actually usable.
raise Errors::SyncedFolderUnusable, type: data[:type].to_s
if !opts[:disable_usable_check]
if !impl_class[0].new.usable?(machine, true)
# Verify that explicitly defined shared folder types are
# actually usable.
raise Errors::SyncedFolderUnusable, type: data[:type].to_s
end
end
end
@ -126,7 +138,7 @@ module Vagrant
# If we have folders with the "default" key, then determine the
# most appropriate implementation for this.
if folders.has_key?("") && !folders[""].empty?
if folders.key?("") && !folders[""].empty?
default_impl = default_synced_folder_type(machine, plugins)
if !default_impl
types = plugins.to_hash.keys.map { |t| t.to_s }.sort.join(", ")

View File

@ -24,13 +24,13 @@ module Vagrant
# Tracks whether we were configured to provision
config_enabled = true
config_enabled = env[:provision_enabled] if env.has_key?(:provision_enabled)
config_enabled = env[:provision_enabled] if env.key?(:provision_enabled)
# Check if we already provisioned, and if so, disable the rest
provision_enabled = true
ignore_sentinel = true
if env.has_key?(:provision_ignore_sentinel)
if env.key?(:provision_ignore_sentinel)
ignore_sentinel = env[:provision_ignore_sentinel]
end
if ignore_sentinel
@ -69,7 +69,7 @@ module Vagrant
end
# Store the value so that other actions can use it
env[:provision_enabled] = provision_enabled if !env.has_key?(:provision_enabled)
env[:provision_enabled] = provision_enabled if !env.key?(:provision_enabled)
# Ask the provisioners to modify the configuration if needed
provisioner_instances(env).each do |p, _|
@ -103,14 +103,20 @@ module Vagrant
provisioner_instances(env).each do |p, options|
type_name = type_map[p]
next if env[:provision_types] && \
!env[:provision_types].include?(type_name)
!env[:provision_types].include?(type_name) && \
!env[:provision_types].include?(options[:name])
# Don't run if sentinel is around and we're not always running
next if !provision_enabled && options[:run] != :always
name = type_name
if options[:name]
name = "#{options[:name]} (#{type_name})"
end
env[:ui].info(I18n.t(
"vagrant.actions.vm.provision.beginning",
provisioner: type_name))
provisioner: name))
env[:hook].call(:provisioner_run, env.merge(
callable: method(:run_provisioner),

View File

@ -31,11 +31,6 @@ module Vagrant
info[:private_key_path] ||= []
# Check SSH key permissions
info[:private_key_path].each do |path|
SSH.check_key_permissions(Pathname.new(path))
end
if info[:private_key_path].empty? && info[:password]
env[:ui].warn(I18n.t("vagrant.ssh_exec_password"))
end

View File

@ -29,11 +29,6 @@ module Vagrant
info[:private_key_path] ||= []
# Check SSH key permissions
info[:private_key_path].each do |path|
SSH.check_key_permissions(Pathname.new(path))
end
if info[:private_key_path].empty?
raise Errors::SSHRunRequiresKeys
end

View File

@ -76,8 +76,10 @@ module Vagrant
# Go through each folder and prepare the folders
folders.each do |impl, impl_name, fs|
@logger.info("Invoking synced folder prepare for: #{impl_name}")
impl.prepare(env[:machine], fs, impl_opts(impl_name, env))
if !env[:synced_folders_disable]
@logger.info("Invoking synced folder prepare for: #{impl_name}")
impl.prepare(env[:machine], fs, impl_opts(impl_name, env))
end
end
# Continue, we need the VM to be booted.

View File

@ -1,4 +1,5 @@
require 'fileutils'
require "pathname"
require 'vagrant/util/safe_chdir'
require 'vagrant/util/subprocess'
@ -28,14 +29,18 @@ module Vagrant
def call(env)
@env = env
file_name = File.basename(@env["package.output"].to_s)
raise Errors::PackageOutputDirectory if File.directory?(tar_path)
raise Errors::PackageOutputExists if File.exist?(tar_path)
raise Errors::PackageOutputExists, file_name:file_name if File.exist?(tar_path)
raise Errors::PackageRequiresDirectory if !env["package.directory"] ||
!File.directory?(env["package.directory"])
@app.call(env)
@env[:ui].info I18n.t("vagrant.actions.general.package.compressing", tar_path: tar_path)
copy_include_files
setup_private_key
compress
end
@ -81,11 +86,6 @@ module Vagrant
# Compress the exported file into a package
def compress
@env[:ui].info I18n.t("vagrant.actions.general.package.compressing", tar_path: tar_path)
# Copy over the included files
copy_include_files
# Get the output path. We have to do this up here so that the
# pwd returns the proper thing.
output_path = tar_path.to_s
@ -100,6 +100,39 @@ module Vagrant
end
end
# This will copy the generated private key into the box and use
# it for SSH by default. We have to do this because we now generate
# random keypairs on boot, so packaged boxes would stop working
# without this.
def setup_private_key
# If we don't have machine, we do nothing (weird)
return if !@env[:machine]
# If we don't have a data dir, we also do nothing (base package)
return if !@env[:machine].data_dir
# If we don't have a generated private key, we do nothing
path = @env[:machine].data_dir.join("private_key")
return if !path.file?
# Copy it into our box directory
dir = Pathname.new(@env["package.directory"])
new_path = dir.join("vagrant_private_key")
FileUtils.cp(path, new_path)
# Append it to the Vagrantfile (or create a Vagrantfile)
vf_path = dir.join("Vagrantfile")
mode = "w+"
mode = "a" if vf_path.file?
vf_path.open(mode) do |f|
f.binmode
f.puts
f.puts %Q[Vagrant.configure("2") do |config|]
f.puts %Q[ config.ssh.private_key_path = File.expand_path("../vagrant_private_key", __FILE__)]
f.puts %Q[end]
end
end
# Path to the final box output file
def tar_path
File.expand_path(@env["package.output"], FileUtils.pwd)

View File

@ -49,7 +49,7 @@ module Vagrant
# Run the action chain in a busy block, marking the environment as
# interrupted if a SIGINT occurs, and exiting cleanly once the
# chain has been run.
ui = environment[:ui] if environment.has_key?(:ui)
ui = environment[:ui] if environment.key?(:ui)
int_callback = lambda do
if environment[:interrupted]
ui.error I18n.t("vagrant.actions.runner.exit_immediately") if ui
@ -62,7 +62,7 @@ module Vagrant
end
# We place a process lock around every action that is called
@logger.info("Running action: #{callable_id}")
@logger.info("Running action: #{environment[:action_name]} #{callable_id}")
Util::Busy.busy(int_callback) { callable.call(environment) }
# Return the environment in case there are things in there that

View File

@ -173,7 +173,7 @@ module Vagrant
Util::SafeChdir.safe_chdir(@directory) do
# Find all the files in our current directory and tar it up!
files = Dir.glob(File.join(".", "**", "*"))
files = Dir.glob(File.join(".", "**", "*")).select { |f| File.file?(f) }
# Package!
Util::Subprocess.execute("bsdtar", "-czf", path.to_s, *files)

View File

@ -29,7 +29,7 @@ module Vagrant
# * BOX_NAME - The name of the box. This is a logical name given by
# the user of Vagrant.
# * PROVIDER - The provider that the box was built for (VirtualBox,
# VMWare, etc.).
# VMware, etc.).
# * metadata.json - A simple JSON file that at the bare minimum
# contains a "provider" key that matches the provider for the
# box. This metadata JSON, however, can contain anything.
@ -225,6 +225,7 @@ module Vagrant
# we have.
child.children(true).each do |versiondir|
next if !versiondir.directory?
next if versiondir.basename.to_s.start_with?(".")
version = versiondir.basename.to_s
@ -270,6 +271,8 @@ module Vagrant
versions = box_directory.children(true).map do |versiondir|
next if !versiondir.directory?
next if versiondir.basename.to_s.start_with?(".")
version = versiondir.basename.to_s
Gem::Version.new(version)
end.compact

View File

@ -165,7 +165,7 @@ module Vagrant
@cap_logger.debug("Checking in: #{host_name}")
caps = @cap_caps[host_name]
if caps && caps.has_key?(cap_name)
if caps && caps.key?(cap_name)
@cap_logger.debug("Found cap: #{cap_name} in #{host_name}")
return caps[cap_name]
end

View File

@ -49,7 +49,7 @@ module Vagrant
# Gather the procs for every source, since that is what we care about.
procs = []
sources.each do |source|
if !@proc_cache.has_key?(source)
if !@proc_cache.key?(source)
# Load the procs for this source and cache them. This caching
# avoids the issue where a file may have side effects when loading
# and loading it multiple times causes unexpected behavior.
@ -92,10 +92,10 @@ module Vagrant
errors = []
order.each do |key|
next if !@sources.has_key?(key)
next if !@sources.key?(key)
@sources[key].each do |version, proc|
if !@config_cache.has_key?(proc)
if !@config_cache.key?(proc)
@logger.debug("Loading from: #{key} (evaluating)")
# Get the proper version loader for this version and load
@ -209,9 +209,16 @@ module Vagrant
@logger.error("Vagrantfile load error: #{e.message}")
@logger.error(e.backtrace.join("\n"))
line = "(unknown)"
if e.backtrace && e.backtrace[0]
line = e.backtrace[0].split(":")[1]
end
# Report the generic exception
raise Errors::VagrantfileLoadError,
path: path,
line: line,
exception_class: e.class,
message: e.message
end
end

View File

@ -60,7 +60,7 @@ module Vagrant
new_keys = new_state["keys"]
keys = {}
old_keys.each do |key, old_value|
if new_keys.has_key?(key)
if new_keys.key?(key)
# We need to do a merge, which we expect to be available
# on the config class itself.
keys[key] = old_value.merge(new_keys[key])
@ -72,7 +72,7 @@ module Vagrant
new_keys.each do |key, new_value|
# Add in the keys that the new class has that we haven't merged.
if !keys.has_key?(key)
if !keys.key?(key)
keys[key] = new_value.dup
end
end

View File

@ -20,7 +20,7 @@ module Vagrant
# used for Vagrant and load the proper configuration classes for
# each.
def method_missing(name, *args)
return @keys[name] if @keys.has_key?(name)
return @keys[name] if @keys.key?(name)
config_klass = @config_map[name.to_sym]
if config_klass

View File

@ -70,7 +70,7 @@ module Vagrant
new_keys = new_state["keys"]
keys = {}
old_keys.each do |key, old_value|
if new_keys.has_key?(key)
if new_keys.key?(key)
# We need to do a merge, which we expect to be available
# on the config class itself.
keys[key] = old_value.merge(new_keys[key])
@ -82,7 +82,7 @@ module Vagrant
new_keys.each do |key, new_value|
# Add in the keys that the new class has that we haven't merged.
if !keys.has_key?(key)
if !keys.key?(key)
keys[key] = new_value.dup
end
end

View File

@ -22,7 +22,7 @@ module Vagrant
# used for Vagrant and load the proper configuration classes for
# each.
def method_missing(name, *args)
return @keys[name] if @keys.has_key?(name)
return @keys[name] if @keys.key?(name)
config_klass = @config_map[name.to_sym]
if config_klass
@ -41,7 +41,7 @@ module Vagrant
# mutate itself.
def finalize!
@config_map.each do |key, klass|
if !@keys.has_key?(key)
if !@keys.key?(key)
@keys[key] = klass.new
end
end
@ -102,9 +102,9 @@ module Vagrant
# This sets the internal state. This is used by the core to do some
# merging logic and shouldn't be used by the general public.
def __set_internal_state(state)
@config_map = state["config_map"] if state.has_key?("config_map")
@keys = state["keys"] if state.has_key?("keys")
@missing_key_calls = state["missing_key_calls"] if state.has_key?("missing_key_calls")
@config_map = state["config_map"] if state.key?("config_map")
@keys = state["keys"] if state.key?("keys")
@missing_key_calls = state["missing_key_calls"] if state.key?("missing_key_calls")
end
end
end

View File

@ -80,7 +80,7 @@ module Vagrant
}.merge(opts || {})
# Set the default working directory to look for the vagrantfile
opts[:cwd] ||= ENV["VAGRANT_CWD"] if ENV.has_key?("VAGRANT_CWD")
opts[:cwd] ||= ENV["VAGRANT_CWD"] if ENV.key?("VAGRANT_CWD")
opts[:cwd] ||= Dir.pwd
opts[:cwd] = Pathname.new(opts[:cwd])
if !opts[:cwd].directory?
@ -94,7 +94,7 @@ module Vagrant
# Set the Vagrantfile name up. We append "Vagrantfile" and "vagrantfile" so that
# those continue to work as well, but anything custom will take precedence.
opts[:vagrantfile_name] ||= ENV["VAGRANT_VAGRANTFILE"] if \
ENV.has_key?("VAGRANT_VAGRANTFILE")
ENV.key?("VAGRANT_VAGRANTFILE")
opts[:vagrantfile_name] = [opts[:vagrantfile_name]] if \
opts[:vagrantfile_name] && !opts[:vagrantfile_name].is_a?(Array)
@ -164,6 +164,15 @@ module Vagrant
@local_data_path = Pathname.new(File.expand_path(opts[:local_data_path], @cwd))
end
# If we have a root path, load the ".vagrantplugins" file.
if root_path
plugins_file = root_path.join(".vagrantplugins")
if plugins_file.file?
@logger.info("Loading plugins file: #{plugins_file}")
load plugins_file
end
end
setup_local_data_path
# Setup the default private key
@ -298,7 +307,7 @@ module Vagrant
# @return [Symbol] Name of the default provider.
def default_provider(**opts)
opts[:exclude] = Set.new(opts[:exclude]) if opts[:exclude]
opts[:force_default] = true if !opts.has_key?(:force_default)
opts[:force_default] = true if !opts.key?(:force_default)
default = ENV["VAGRANT_DEFAULT_PROVIDER"]
default = nil if default == ""
@ -308,6 +317,30 @@ module Vagrant
# that (the default behavior)
return default if default && opts[:force_default]
# Determine the config to use to look for provider definitions. By
# default it is the global but if we're targeting a specific machine,
# then look there.
root_config = vagrantfile.config
if opts[:machine]
machine_info = vagrantfile.machine_config(opts[:machine], nil, nil)
root_config = machine_info[:config]
end
# Get the list of providers within our configuration and assign
# a priority to each in the order they exist so that we try these first.
config = {}
root_config.vm.__providers.reverse.each_with_index do |key, idx|
config[key] = idx
end
# Determine the max priority so that we can add the config priority
# to that to make sure it is highest.
max_priority = 0
Vagrant.plugin("2").manager.providers.each do |key, data|
priority = data[1][:priority]
max_priority = priority if priority > max_priority
end
ordered = []
Vagrant.plugin("2").manager.providers.each do |key, data|
impl = data[0]
@ -316,10 +349,19 @@ module Vagrant
# Skip excluded providers
next if opts[:exclude] && opts[:exclude].include?(key)
# Skip providers that can't be defaulted
next if popts.has_key?(:defaultable) && !popts[:defaultable]
# Skip providers that can't be defaulted, unless they're in our
# config, in which case someone made our decision for us.
if !config.key?(key)
next if popts.key?(:defaultable) && !popts[:defaultable]
end
ordered << [popts[:priority], key, impl, popts]
# The priority is higher if it is in our config. Otherwise, it is
# the priority it set PLUS the length of the config to make sure it
# is never higher than the configuration keys.
priority = popts[:priority]
priority = config[key] + max_priority if config.key?(key)
ordered << [priority, key, impl, popts]
end
# Order the providers by priority. Higher values are tried first.
@ -336,8 +378,8 @@ module Vagrant
return key if impl.usable?(false)
end
# If all else fails, return VirtualBox
return :virtualbox
# No providers available is a critical error for Vagrant.
raise Errors::NoDefaultProvider
end
# Returns the collection of boxes for the environment.
@ -501,6 +543,41 @@ module Vagrant
end
end
# This executes the push with the given name, raising any exceptions that
# occur.
#
# Precondition: the push is not nil and exists.
def push(name)
@logger.info("Getting push: #{name}")
name = name.to_sym
pushes = self.vagrantfile.config.push.__compiled_pushes
if !pushes.key?(name)
raise Vagrant::Errors::PushStrategyNotDefined,
name: name,
pushes: pushes.keys
end
strategy, config = pushes[name]
push_registry = Vagrant.plugin("2").manager.pushes
klass, _ = push_registry.get(strategy)
if klass.nil?
raise Vagrant::Errors::PushStrategyNotLoaded,
name: strategy,
pushes: push_registry.keys
end
klass.new(self, config).push
end
# The list of pushes defined in this Vagrantfile.
#
# @return [Array<Symbol>]
def pushes
self.vagrantfile.config.push.__compiled_pushes.keys
end
# This returns a machine with the proper provider for this environment.
# The machine named by `name` must be in this environment.
#
@ -523,7 +600,7 @@ module Vagrant
@machines.delete(cache_key)
end
if @machines.has_key?(cache_key)
if @machines.key?(cache_key)
@logger.info("Returning cached machine: #{name} (#{provider})")
return @machines[cache_key]
end
@ -531,10 +608,8 @@ module Vagrant
@logger.info("Uncached load of machine.")
# Determine the machine data directory and pass it to the machine.
# XXX: Permissions error here.
machine_data_path = @local_data_path.join(
"machines/#{name}/#{provider}")
FileUtils.mkdir_p(machine_data_path)
# Create the machine and cache it for future calls. This will also
# return the machine from this method.
@ -786,7 +861,10 @@ module Vagrant
# This upgrades a home directory that was in the v1.1 format to the
# v1.5 format. It will raise exceptions if anything fails.
def upgrade_home_path_v1_1
@ui.ask(I18n.t("vagrant.upgrading_home_path_v1_5"))
if !ENV["VAGRANT_UPGRADE_SILENT_1_5"]
@ui.ask(I18n.t("vagrant.upgrading_home_path_v1_5"))
end
collection = BoxCollection.new(
@home_path.join("boxes"), temp_dir_root: tmp_path)
collection.upgrade_v1_1_v1_5

View File

@ -316,6 +316,10 @@ module Vagrant
error_key(:corrupt_machine_index)
end
class DarwinMountFailed < VagrantError
error_key(:darwin_mount_failed)
end
class DarwinNFSMountFailed < VagrantError
error_key(:darwin_nfs_mount_failed)
end
@ -476,6 +480,10 @@ module Vagrant
error_key(:nfs_client_not_installed_in_guest)
end
class NoDefaultProvider < VagrantError
error_key(:no_default_provider)
end
class NoDefaultSyncedFolderImpl < VagrantError
error_key(:no_default_synced_folder_impl)
end
@ -552,6 +560,22 @@ module Vagrant
error_key(:plugin_uninstall_system)
end
class PushesNotDefined < VagrantError
error_key(:pushes_not_defined)
end
class PushStrategyNotDefined < VagrantError
error_key(:push_strategy_not_defined)
end
class PushStrategyNotLoaded < VagrantError
error_key(:push_strategy_not_loaded)
end
class PushStrategyNotProvided < VagrantError
error_key(:push_strategy_not_provided)
end
class RSyncError < VagrantError
error_key(:rsync_error)
end
@ -616,6 +640,10 @@ module Vagrant
error_key(:ssh_invalid_shell)
end
class SSHInsertKeyUnsupported < VagrantError
error_key(:ssh_insert_key_unsupported)
end
class SSHIsPuttyLink < VagrantError
error_key(:ssh_is_putty_link)
end
@ -692,6 +720,10 @@ module Vagrant
error_key(:vboxmanage_error)
end
class VBoxManageLaunchError < VagrantError
error_key(:vboxmanage_launch_error)
end
class VBoxManageNotFoundError < VagrantError
error_key(:vboxmanage_not_found_error)
end
@ -728,6 +760,18 @@ module Vagrant
error_key(:virtualbox_no_name)
end
class VirtualBoxNameExists < VagrantError
error_key(:virtualbox_name_exists)
end
class VirtualBoxUserMismatch < VagrantError
error_key(:virtualbox_user_mismatch)
end
class VirtualBoxVersionEmpty < VagrantError
error_key(:virtualbox_version_empty)
end
class VMBaseMacNotSpecified < VagrantError
error_key(:no_base_mac, "vagrant.actions.vm.match_mac")
end

View File

@ -1,3 +1,5 @@
require_relative "util/ssh"
require "digest/md5"
require "thread"
@ -6,7 +8,7 @@ require "log4r"
module Vagrant
# This represents a machine that Vagrant manages. This provides a singular
# API for querying the state and making state changes to the machine, which
# is backed by any sort of provider (VirtualBox, VMWare, etc.).
# is backed by any sort of provider (VirtualBox, VMware, etc.).
class Machine
# The box that is backing this machine.
#
@ -133,6 +135,12 @@ module Vagrant
@logger.debug("Eager loading WinRM communicator to avoid GH-3390")
communicate
end
# If the ID is the special not created ID, then set our ID to
# nil so that we destroy all our data.
if state.id == MachineState::NOT_CREATED_ID
self.id = nil
end
end
# This calls an action on the provider. The provider may or may not
@ -142,20 +150,29 @@ module Vagrant
# @param [Hash] extra_env This data will be passed into the action runner
# as extra data set on the environment hash for the middleware
# runner.
def action(name, extra_env=nil)
def action(name, opts=nil)
@logger.info("Calling action: #{name} on provider #{@provider}")
opts ||= {}
# Determine whether we lock or not
lock = true
lock = opts.delete(:lock) if opts.key?(:lock)
# Extra env keys are the remaining opts
extra_env = opts.dup
# Create a deterministic ID for this machine
vf = nil
vf = @env.vagrantfile_name[0] if @env.vagrantfile_name
id = Digest::MD5.hexdigest(
"#{@env.root_path}#{vf}#{@name}")
"#{@env.root_path}#{vf}#{@env.local_data_path}#{@name}")
# We only lock if we're not executing an SSH action. In the future
# we will want to do more fine-grained unlocking in actions themselves
# but for a 1.6.2 release this will work.
locker = Proc.new { |*args, &block| block.call }
locker = @env.method(:lock) if !name.to_s.start_with?("ssh")
locker = @env.method(:lock) if lock && !name.to_s.start_with?("ssh")
# Lock this machine for the duration of this action
locker.call("machine-action-#{id}") do
@ -170,6 +187,7 @@ module Vagrant
provider: @provider.to_s
end
# Call the action
action_raw(name, callable, extra_env)
end
rescue Errors::EnvironmentLockedError
@ -261,6 +279,13 @@ module Vagrant
end
end
if uid_file
# Write the user id that created this machine
uid_file.open("w+") do |f|
f.write(Process.uid.to_s)
end
end
# If we don't have a UUID, then create one
if index_uuid.nil?
# Create the index entry and save it
@ -293,6 +318,7 @@ module Vagrant
else
# Delete the file, since the machine is now destroyed
id_file.delete if id_file && id_file.file?
uid_file.delete if uid_file && uid_file.file?
# If we have a UUID associated with the index, remove it
uuid = index_uuid
@ -342,6 +368,7 @@ module Vagrant
# This reloads the ID of the underlying machine.
def reload
old_id = @id
@id = nil
if @data_dir
@ -350,6 +377,13 @@ module Vagrant
id_file = @data_dir.join("id")
@id = id_file.read.chomp if id_file.file?
end
if @id != old_id && @provider
# It changed, notify the provider
@provider.machine_id_changed
end
@id
end
# This returns the SSH info for accessing this machine. This SSH info
@ -403,6 +437,8 @@ module Vagrant
info[:forward_agent] = @config.ssh.forward_agent
info[:forward_x11] = @config.ssh.forward_x11
info[:ssh_command] = @config.ssh.ssh_command if @config.ssh.ssh_command
# Add in provided proxy command config
info[:proxy_command] = @config.ssh.proxy_command if @config.ssh.proxy_command
@ -418,7 +454,7 @@ module Vagrant
end
# If we have a private key in our data dir, then use that
if @data_dir
if @data_dir && !@config.ssh.private_key_path
data_private_key = @data_dir.join("private_key")
if data_private_key.file?
info[:private_key_path] = [data_private_key.to_s]
@ -434,6 +470,14 @@ module Vagrant
File.expand_path(path, @env.root_path)
end
# Check that the private key permissions are valid
info[:private_key_path].each do |path|
key_path = Pathname.new(path)
if key_path.exist?
Vagrant::Util::SSH.check_key_permissions(key_path)
end
end
# Return the final compiled SSH info data
info
end
@ -446,12 +490,6 @@ module Vagrant
result = @provider.state
raise Errors::MachineStateInvalid if !result.is_a?(MachineState)
# If the ID is the special not created ID, then set our ID to
# nil so that we destroy all our data.
if result.id == MachineState::NOT_CREATED_ID
self.id = nil
end
# Update our state cache if we have a UUID and an entry in the
# master index.
uuid = index_uuid
@ -467,6 +505,17 @@ module Vagrant
result
end
# Returns the user ID that created this machine. This is specific to
# the host machine that this was created on.
#
# @return [String]
def uid
path = uid_file
return nil if !path
return nil if !path.file?
return uid_file.read.chomp
end
# Temporarily changes the machine UI. This is useful if you want
# to execute an {#action} with a different UI.
def with_ui(ui)
@ -480,5 +529,13 @@ module Vagrant
end
end
end
protected
# Returns the path to the file that stores the UID.
def uid_file
return nil if !@data_dir
@data_dir.join("creator_uid")
end
end
end

View File

@ -143,7 +143,7 @@ module Vagrant
# If we already have a newer version in our list of installed,
# then ignore it
next if installed_map.has_key?(spec.name) &&
next if installed_map.key?(spec.name) &&
installed_map[spec.name].version >= spec.version
installed_map[spec.name] = spec

View File

@ -61,7 +61,7 @@ module Vagrant
#
# @return [Boolean]
def has_plugin?(name)
@data["installed"].has_key?(name)
@data["installed"].key?(name)
end
# Remove a plugin that is installed from the state file.

View File

@ -16,6 +16,7 @@ module Vagrant
autoload :Manager, "vagrant/plugin/v2/manager"
autoload :Plugin, "vagrant/plugin/v2/plugin"
autoload :Provider, "vagrant/plugin/v2/provider"
autoload :Push, "vagrant/plugin/v2/push"
autoload :Provisioner, "vagrant/plugin/v2/provisioner"
autoload :SyncedFolder, "vagrant/plugin/v2/synced_folder"
end

View File

@ -58,7 +58,7 @@ module Vagrant
opts.parse!(argv)
return argv
rescue OptionParser::InvalidOption
rescue OptionParser::InvalidOption, OptionParser::MissingArgument
raise Errors::CLIInvalidOptions, help: opts.help.chomp
end
@ -162,7 +162,7 @@ module Vagrant
end
# Use the default provider if nothing else
provider_to_use ||= @env.default_provider
provider_to_use ||= @env.default_provider(machine: name)
# Get the right machine with the right provider
@env.machine(name, provider_to_use)
@ -224,6 +224,15 @@ module Vagrant
@logger.info("With machine: #{machine.name} (#{machine.provider.inspect})")
yield machine
# Call the state method so that we update our index state. Don't
# worry about exceptions here, since we just care about updating
# the cache.
begin
# Called for side effects
machine.state
rescue Errors::VagrantError
end
end
end

View File

@ -54,6 +54,11 @@ module Vagrant
# @return [Hash<Symbol, Registry>]
attr_reader :provider_capabilities
# This contains all the push implementations by name.
#
# @return [Registry<Symbol, Array<Class, Hash>>]
attr_reader :pushes
# This contains all the synced folder implementations by name.
#
# @return [Registry<Symbol, Array<Class, Integer>>]
@ -71,6 +76,7 @@ module Vagrant
@host_capabilities = Hash.new { |h, k| h[k] = Registry.new }
@providers = Registry.new
@provider_capabilities = Hash.new { |h, k| h[k] = Registry.new }
@pushes = Registry.new
@synced_folders = Registry.new
end
end

View File

@ -172,6 +172,28 @@ module Vagrant
end
end
# This returns all registered pushes.
#
# @return [Registry]
def pushes
Registry.new.tap do |result|
@registered.each do |plugin|
result.merge!(plugin.components.pushes)
end
end
end
# This returns all the config classes for the various pushes.
#
# @return [Registry]
def push_configs
Registry.new.tap do |result|
@registered.each do |plugin|
result.merge!(plugin.components.configs[:push])
end
end
end
# This returns all synced folder implementations.
#
# @return [Registry]

View File

@ -88,7 +88,7 @@ module Vagrant
end
# By default, the command is primary
opts[:primary] = true if !opts.has_key?(:primary)
opts[:primary] = true if !opts.key?(:primary)
# Register the command
components.commands.register(name.to_sym) do
@ -221,6 +221,18 @@ module Vagrant
data[:provisioners]
end
# Registers additional pushes to be available.
#
# @param [String] name Name of the push.
# @param [Hash] options List of options for the push.
def self.push(name, options=nil, &block)
components.pushes.register(name.to_sym) do
[block.call, options]
end
nil
end
# Registers additional synced folder implementations.
#
# @param [String] name Name of the implementation.

View File

@ -0,0 +1,27 @@
module Vagrant
module Plugin
module V2
class Push
attr_reader :env
attr_reader :config
# Initializes the pusher with the given environment the push
# configuration.
#
# @param [Environment] env
# @param [Object] config Push configuration
def initialize(env, config)
@env = env
@config = config
end
# This is the method called when the actual pushing should be
# done.
#
# No return value is expected.
def push
end
end
end
end
end

View File

@ -22,8 +22,8 @@ module Vagrant
# This will evaluate the block given to `register` and return the
# resulting value.
def get(key)
return nil if !@items.has_key?(key)
return @results_cache[key] if @results_cache.has_key?(key)
return nil if !@items.key?(key)
return @results_cache[key] if @results_cache.key?(key)
@results_cache[key] = @items[key].call
end
alias :[] :get
@ -31,10 +31,11 @@ module Vagrant
# Checks if the given key is registered with the registry.
#
# @return [Boolean]
def has_key?(key)
@items.has_key?(key)
def key?(key)
@items.key?(key)
end
alias_method :has_key?, :key?
# Returns an array populated with the keys of this object.
#
# @return [Array]
@ -49,6 +50,21 @@ module Vagrant
end
end
# Return the number of elements in this registry.
#
# @return [Fixnum]
def length
@items.keys.length
end
alias_method :size, :length
# Checks if this registry has any items.
#
# @return [Boolean]
def empty?
@items.keys.empty?
end
# Merge one registry with another and return a completely new
# registry. Note that the result cache is completely busted, so
# any gets on the new registry will result in a cache miss.

View File

@ -5,12 +5,12 @@ require "thread"
module Vagrant
@@global_lock = Mutex.new
# This is the default endpoint of the Vagrant Cloud in
# This is the default endpoint of the Atlas in
# use. API calls will be made to this for various functions
# of Vagrant that may require remote access.
#
# @return [String]
DEFAULT_SERVER_URL = "https://vagrantcloud.com"
DEFAULT_SERVER_URL = "https://atlas.hashicorp.com"
# This holds a global lock for the duration of the block. This should
# be invoked around anything that is modifying process state (such as
@ -38,11 +38,11 @@ module Vagrant
ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"]
end
# This returns whether or not 3rd party plugins should be loaded.
# This returns whether or not 3rd party plugins should and can be loaded.
#
# @return [Boolean]
def self.plugins_enabled?
!ENV["VAGRANT_NO_PLUGINS"]
!ENV["VAGRANT_NO_PLUGINS"] && $vagrant_bundler_runtime
end
# Whether or not super quiet mode is enabled. This is ill-advised.
@ -52,6 +52,13 @@ module Vagrant
!!ENV["VAGRANT_I_KNOW_WHAT_IM_DOING_PLEASE_BE_QUIET"]
end
# The current log level for Vagrant
#
# @return [String]
def self.log_level
ENV["VAGRANT_LOG"]
end
# Returns the URL prefix to the server.
#
# @return [String]

View File

@ -21,9 +21,22 @@ module Vagrant
# specific. See the implementation for more docs.
attr_accessor :opts
# @return [IO] UI input. Defaults to `$stdin`.
attr_accessor :stdin
# @return [IO] UI output. Defaults to `$stdout`.
attr_accessor :stdout
# @return [IO] UI error output. Defaults to `$stderr`.
attr_accessor :stderr
def initialize
@logger = Log4r::Logger.new("vagrant::ui::interface")
@opts = {}
@stdin = $stdin
@stdout = $stdout
@stderr = $stderr
end
def initialize_copy(original)
@ -50,6 +63,11 @@ module Vagrant
define_method(method) { |*args| }
end
# @return [false]
def color?
return false
end
# For machine-readable output.
#
# @param [String] type The type of the data
@ -132,23 +150,23 @@ module Vagrant
super(message)
# We can't ask questions when the output isn't a TTY.
raise Errors::UIExpectsTTY if !$stdin.tty? && !Vagrant::Util::Platform.cygwin?
raise Errors::UIExpectsTTY if !@stdin.tty? && !Vagrant::Util::Platform.cygwin?
# Setup the options so that the new line is suppressed
opts ||= {}
opts[:echo] = true if !opts.has_key?(:echo)
opts[:new_line] = false if !opts.has_key?(:new_line)
opts[:prefix] = false if !opts.has_key?(:prefix)
opts[:echo] = true if !opts.key?(:echo)
opts[:new_line] = false if !opts.key?(:new_line)
opts[:prefix] = false if !opts.key?(:prefix)
# Output the data
say(:info, message, opts)
input = nil
if opts[:echo]
input = $stdin.gets
if opts[:echo] || !@stdin.respond_to?(:noecho)
input = @stdin.gets
else
begin
input = $stdin.noecho(&:gets)
input = @stdin.noecho(&:gets)
# Output a newline because without echo, the newline isn't
# echoed either.
@ -206,7 +224,7 @@ module Vagrant
# Determine the proper IO channel to send this message
# to based on the type of the message
channel = type == :error || opts[:channel] == :error ? $stderr : $stdout
channel = type == :error || opts[:channel] == :error ? @stderr : @stdout
# Output! We wrap this in a lock so that it safely outputs only
# one line at a time. We wrap this in a thread because as of Ruby 2.0
@ -249,7 +267,7 @@ module Vagrant
class_eval <<-CODE
def #{method}(message, *args, **opts)
super(message)
if !@ui.opts.has_key?(:bold) && !opts.has_key?(:bold)
if !@ui.opts.key?(:bold) && !opts.key?(:bold)
opts[:bold] = #{method.inspect} != :detail && \
#{method.inspect} != :ask
end
@ -284,7 +302,7 @@ module Vagrant
opts = self.opts.merge(opts)
prefix = ""
if !opts.has_key?(:prefix) || opts[:prefix]
if !opts.key?(:prefix) || opts[:prefix]
prefix = OUTPUT_PREFIX
prefix = " " * OUTPUT_PREFIX.length if \
type == :detail || type == :ask || opts[:prefix_spaces]
@ -294,7 +312,7 @@ module Vagrant
return message if prefix.empty?
target = @prefix
target = opts[:target] if opts.has_key?(:target)
target = opts[:target] if opts.key?(:target)
# Get the lines. The first default is because if the message
# is an empty string, then we want to still use the empty string.
@ -323,6 +341,11 @@ module Vagrant
white: 37,
}
# @return [true]
def color?
return true
end
# This is called by `say` to format the message for output.
def format_message(type, message, **opts)
# Get the format of the message before adding color.

View File

@ -2,6 +2,7 @@ module Vagrant
module Util
autoload :Busy, 'vagrant/util/busy'
autoload :Counter, 'vagrant/util/counter'
autoload :Env, 'vagrant/util/env'
autoload :HashWithIndifferentAccess, 'vagrant/util/hash_with_indifferent_access'
autoload :Platform, 'vagrant/util/platform'
autoload :Retryable, 'vagrant/util/retryable'

View File

@ -29,8 +29,8 @@ module Vagrant
begin
url = URI.parse(@source)
if url.scheme && url.scheme.start_with?("http") && url.user
auth = "#{url.user}"
auth += ":#{url.password}" if url.password
auth = "#{URI.unescape(url.user)}"
auth += ":#{URI.unescape(url.password)}" if url.password
url.user = nil
url.password = nil
options[:auth] ||= auth
@ -49,6 +49,7 @@ module Vagrant
@insecure = options[:insecure]
@ui = options[:ui]
@client_cert = options[:client_cert]
@location_trusted = options[:location_trusted]
end
# This executes the actual download, downloading the source file
@ -58,18 +59,15 @@ module Vagrant
# If this method returns without an exception, the download
# succeeded. An exception will be raised if the download failed.
def download!
options, subprocess_options = self.options
options += ["--output", @destination]
options << @source
# This variable can contain the proc that'll be sent to
# the subprocess execute.
data_proc = nil
extra_subprocess_opts = {}
if @ui
# If we're outputting progress, then setup the subprocess to
# tell us output so we can parse it out.
subprocess_options[:notify] = :stderr
extra_subprocess_opts[:notify] = :stderr
progress_data = ""
progress_regexp = /(\r(.+?))\r/
@ -121,8 +119,31 @@ module Vagrant
@logger.info(" -- Source: #{@source}")
@logger.info(" -- Destination: #{@destination}")
retried = false
begin
# Get the command line args and the subprocess opts based
# on our downloader settings.
options, subprocess_options = self.options
options += ["--output", @destination]
options << @source
# Merge in any extra options we set
subprocess_options.merge!(extra_subprocess_opts)
# Go!
execute_curl(options, subprocess_options, &data_proc)
rescue Errors::DownloaderError => e
# If we already retried, raise it.
raise if retried
# If its any error other than 33, it is an error.
raise if e.extra_data[:exit_code].to_i != 33
# Exit code 33 means that the server doesn't support ranges.
# In this case, try again without resume.
@continue = false
retried = true
retry
ensure
# If we're outputting to the UI, clear the output to
# avoid lingering progress meters.
@ -177,7 +198,9 @@ module Vagrant
@logger.warn("Downloader exit code: #{result.exit_code}")
parts = result.stderr.split(/\n*curl:\s+\(\d+\)\s*/, 2)
parts[1] ||= ""
raise Errors::DownloaderError, message: parts[1].chomp
raise Errors::DownloaderError,
code: result.exit_code,
message: parts[1].chomp
end
result
@ -202,6 +225,7 @@ module Vagrant
options << "--insecure" if @insecure
options << "--cert" << @client_cert if @client_cert
options << "-u" << @auth if @auth
options << "--location-trusted" if @location_trusted
if @headers
Array(@headers).each do |header|

51
lib/vagrant/util/env.rb Normal file
View File

@ -0,0 +1,51 @@
require "bundler"
module Vagrant
module Util
class Env
def self.with_original_env
original_env = ENV.to_hash
ENV.replace(::Bundler::ORIGINAL_ENV) if defined?(::Bundler::ORIGINAL_ENV)
ENV.update(Vagrant.original_env)
yield
ensure
ENV.replace(original_env.to_hash)
end
# Execute the given command, removing any Ruby-specific environment
# variables. This is an "enhanced" version of `Bundler.with_clean_env`,
# which only removes Bundler-specific values. We need to remove all
# values, specifically:
#
# - _ORIGINAL_GEM_PATH
# - GEM_PATH
# - GEM_HOME
# - GEM_ROOT
# - BUNDLE_BIN_PATH
# - BUNDLE_GEMFILE
# - RUBYLIB
# - RUBYOPT
# - RUBY_ENGINE
# - RUBY_ROOT
# - RUBY_VERSION
#
# This will escape Vagrant's environment entirely, which is required if
# calling an executable that lives in another Ruby environment. The
# original environment restored at the end of this call.
#
# @param [Proc] block
# the block to execute with the cleaned environment
def self.with_clean_env
with_original_env do
ENV["MANPATH"] = ENV["BUNDLE_ORIG_MANPATH"]
ENV.delete_if { |k,_| k[0,7] == "BUNDLE_" }
if ENV.has_key? "RUBYOPT"
ENV["RUBYOPT"] = ENV["RUBYOPT"].sub("-rbundler/setup", "")
ENV["RUBYOPT"] = ENV["RUBYOPT"].sub("-I#{File.expand_path('..', __FILE__)}", "")
end
yield
end
end
end
end
end

View File

@ -25,11 +25,11 @@ module Vagrant
# We have to do this since `readpartial` will actually block
# until data is available, which can cause blocking forever
# in some cases.
results = ::IO.select([io], nil, nil, 0.1)
results = ::IO.select([io], nil, nil, 1.0)
break if !results || results[0].empty?
# Read!
data << io.readpartial(READ_CHUNK_SIZE)
data << io.readpartial(READ_CHUNK_SIZE).encode("UTF-8", Encoding.default_external)
else
# Do a simple non-blocking read on the IO object
data << io.read_nonblock(READ_CHUNK_SIZE)

View File

@ -29,7 +29,8 @@ module Vagrant
# to connect.
return true
end
rescue Timeout::Error, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH
rescue Timeout::Error, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, \
Errno::ENETUNREACH, Errno::EACCES
# Any of the above exceptions signal that the port is closed.
return false
end

View File

@ -0,0 +1,56 @@
require "base64"
require "openssl"
require "vagrant/util/retryable"
module Vagrant
module Util
class Keypair
extend Retryable
# Creates an SSH keypair and returns it.
#
# @param [String] password Password for the key, or nil for no password.
# @return [Array<String, String, String>] PEM-encoded public and private key,
# respectively. The final element is the OpenSSH encoded public
# key.
def self.create(password=nil)
# This sometimes fails with RSAError. It is inconsistent and strangely
# sleeps seem to fix it. We just retry this a few times. See GH-5056
rsa_key = nil
retryable(on: OpenSSL::PKey::RSAError, sleep: 2, tries: 5) do
rsa_key = OpenSSL::PKey::RSA.new(2048)
end
public_key = rsa_key.public_key
private_key = rsa_key.to_pem
if password
cipher = OpenSSL::Cipher::Cipher.new('des3')
private_key = rsa_key.to_pem(cipher, password)
end
# Generate the binary necessary for the OpenSSH public key.
binary = [7].pack("N")
binary += "ssh-rsa"
["e", "n"].each do |m|
val = public_key.send(m)
data = val.to_s(2)
first_byte = data[0,1].unpack("c").first
if val < 0
data[0] = [0x80 & first_byte].pack("c")
elsif first_byte < 0
data = 0.chr + data
end
binary += [data.length].pack("N") + data
end
openssh_key = "ssh-rsa #{Base64.encode64(binary).gsub("\n", "")} vagrant"
public_key = public_key.to_pem
return [public_key, private_key, openssh_key]
end
end
end
end

View File

@ -144,11 +144,18 @@ module Vagrant
path
end
# Converts a given path to UNC format by adding a prefix and converting slashes.
# @param [String] path Path to convert to UNC for Windows
# @return [String]
def windows_unc_path(path)
"\\\\?\\" + path.gsub("/", "\\")
end
# Returns a boolean noting whether the terminal supports color.
# output.
def terminal_supports_colors?
if windows?
return true if ENV.has_key?("ANSICON")
return true if ENV.key?("ANSICON")
return true if cygwin?
return true if ENV["TERM"] == "cygwin"
return false

View File

@ -83,7 +83,7 @@ module Vagrant
# underneath the covers. In this case, we tell the user.
if Platform.windows?
r = Subprocess.execute(ssh_path)
if r.stdout.include?("PuTTY Link")
if r.stdout.include?("PuTTY Link") || r.stdout.include?("Plink: command-line connection utility")
raise Errors::SSHIsPuttyLink,
host: ssh_info[:host],
port: ssh_info[:port],
@ -102,12 +102,14 @@ module Vagrant
options[:username] = ssh_info[:username]
options[:private_key_path] = ssh_info[:private_key_path]
log_level = ssh_info[:log_level] || "FATAL"
# Command line options
command_options = [
"-p", options[:port].to_s,
"-o", "Compression=yes",
"-o", "DSAAuthentication=yes",
"-o", "LogLevel=FATAL",
"-o", "LogLevel=#{log_level}",
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null"]
@ -156,17 +158,19 @@ module Vagrant
# we really don't care since both work.
ENV["nodosfilewarning"] = "1" if Platform.cygwin?
ssh = ssh_info[:ssh_command] || 'ssh'
# Invoke SSH with all our options
if !opts[:subprocess]
LOGGER.info("Invoking SSH: #{command_options.inspect}")
SafeExec.exec("ssh", *command_options)
LOGGER.info("Invoking SSH: #{ssh} #{command_options.inspect}")
SafeExec.exec(ssh, *command_options)
return
end
# If we're still here, it means we're supposed to subprocess
# out to ssh rather than exec it.
LOGGER.info("Executing SSH in subprocess: #{command_options.inspect}")
process = ChildProcess.build("ssh", *command_options)
LOGGER.info("Executing SSH in subprocess: #{ssh} #{command_options.inspect}")
process = ChildProcess.build(ssh, *command_options)
process.io.inherit!
process.start
process.wait

View File

@ -25,6 +25,7 @@ module Vagrant
def initialize(*command)
@options = command.last.is_a?(Hash) ? command.pop : {}
@command = command.dup
@command = @command.map { |s| s.encode(Encoding.default_external) }
@command[0] = Which.which(@command[0]) if !File.file?(@command[0])
if !@command[0]
raise Errors::CommandUnavailableWindows, file: command[0] if Platform.windows?
@ -71,23 +72,41 @@ module Vagrant
process.io.stderr = stderr_writer
process.duplex = true
# If we're in an installer on Mac and we're executing a command
# in the installer context, then force DYLD_LIBRARY_PATH to look
# at our libs first.
if Vagrant.in_installer? && Platform.darwin?
# Special installer-related things
if Vagrant.in_installer?
installer_dir = ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"].to_s.downcase
if @command[0].downcase.include?(installer_dir)
@logger.info("Command in the installer. Specifying DYLD_LIBRARY_PATH...")
process.environment["DYLD_LIBRARY_PATH"] =
"#{installer_dir}/lib:#{ENV["DYLD_LIBRARY_PATH"]}"
else
@logger.debug("Command not in installer, not touching env vars.")
# If we're in an installer on Mac and we're executing a command
# in the installer context, then force DYLD_LIBRARY_PATH to look
# at our libs first.
if Platform.darwin?
if @command[0].downcase.include?(installer_dir)
@logger.info("Command in the installer. Specifying DYLD_LIBRARY_PATH...")
process.environment["DYLD_LIBRARY_PATH"] =
"#{installer_dir}/lib:#{ENV["DYLD_LIBRARY_PATH"]}"
else
@logger.debug("Command not in installer, not touching env vars.")
end
if File.setuid?(@command[0]) || File.setgid?(@command[0])
@logger.info("Command is setuid/setgid, clearing DYLD_LIBRARY_PATH")
process.environment["DYLD_LIBRARY_PATH"] = ""
end
end
if File.setuid?(@command[0]) || File.setgid?(@command[0])
@logger.info("Command is setuid/setgid, clearing DYLD_LIBRARY_PATH")
process.environment["DYLD_LIBRARY_PATH"] = ""
# If the command that is being run is not inside the installer, reset
# the original environment - this is required for shelling out to
# other subprocesses that depend on environment variables (like Ruby
# and $GEM_PATH for example)
internal = [installer_dir, Vagrant.user_data_path.to_s.downcase].
any? { |path| @command[0].downcase.include?(path) }
if !internal
@logger.info("Command not in installer, restoring original environment...")
jailbreak(process.environment)
end
else
@logger.info("Vagrant not running in installer, restoring original environment...")
jailbreak(process.environment)
end
# Set the environment on the process if we must
@ -238,6 +257,58 @@ module Vagrant
@stderr = stderr
end
end
private
# This is, quite possibly, the saddest function in all of Vagrant.
#
# If a user is running Vagrant via Bundler (but not via the official
# installer), we want to reset to the "original" environment so that when
# shelling out to other Ruby processes (specifically), the original
# environment is restored. This is super important for things like
# rbenv and chruby, who rely on environment variables to locate gems, but
# Bundler stomps on those environment variables like an angry T-Rex after
# watching Jurassic Park 2 and realizing they replaced you with CGI.
#
# If a user is running in Vagrant via the official installer, BUT trying
# to execute a subprocess *outside* of the installer, we want to reset to
# the "original" environment. In this case, the Vagrant installer actually
# knows what the original environment was and replaces it completely.
#
# Finally, we reset any Bundler-specific environment variables, since the
# subprocess being called could, itself, be Bundler. And Bundler does not
# behave very nicely in these circumstances.
#
# This function was added in Vagrant 1.7.3, but there is a failsafe
# because the author doesn't trust himself that this functionality won't
# break existing assumptions, so users can specify
# `VAGRANT_SKIP_SUBPROCESS_JAILBREAK` and none of the above will happen.
#
# This function modifies the given hash in place!
#
# @return [nil]
def jailbreak(env = {})
return if ENV.key?("VAGRANT_SKIP_SUBPROCESS_JAILBREAK")
env.replace(::Bundler::ORIGINAL_ENV) if defined?(::Bundler::ORIGINAL_ENV)
env.merge!(Vagrant.original_env)
# Bundler does this, so I guess we should as well, since I think it
# other subprocesses that use Bundler will reload it
env["MANPATH"] = ENV["BUNDLE_ORIG_MANPATH"]
# Replace all current environment BUNDLE_ variables to nil
ENV.each do |k,_|
env[k] = nil if k[0,7] == "BUNDLE_"
end
# If RUBYOPT was set, unset it with Bundler
if ENV.key?("RUBYOPT")
env["RUBYOPT"] = ENV["RUBYOPT"].sub("-rbundler/setup", "")
end
nil
end
end
end
end

View File

@ -70,6 +70,10 @@ module Vagrant
# Get the provider configuration from the final loaded configuration
provider_config = config.vm.get_provider_config(provider)
# Create machine data directory if it doesn't exist
# XXX: Permissions error here.
FileUtils.mkdir_p(data_path)
# Create the machine and cache it for future calls. This will also
# return the machine from this method.
return Machine.new(name, provider, provider_cls, provider_config,
@ -114,24 +118,30 @@ module Vagrant
name: name, provider: provider
end
provider_plugin = Vagrant.plugin("2").manager.providers[provider]
if !provider_plugin
raise Errors::ProviderNotFound,
machine: name, provider: provider
end
provider_plugin = nil
provider_cls = nil
provider_options = {}
box_formats = nil
if provider != nil
provider_plugin = Vagrant.plugin("2").manager.providers[provider]
if !provider_plugin
raise Errors::ProviderNotFound,
machine: name, provider: provider
end
provider_cls = provider_plugin[0]
provider_options = provider_plugin[1]
box_formats = provider_options[:box_format] || provider
provider_cls = provider_plugin[0]
provider_options = provider_plugin[1]
box_formats = provider_options[:box_format] || provider
# Test if the provider is usable or not
begin
provider_cls.usable?(true)
rescue Errors::VagrantError => e
raise Errors::ProviderNotUsable,
machine: name.to_s,
provider: provider.to_s,
message: e.to_s
# Test if the provider is usable or not
begin
provider_cls.usable?(true)
rescue Errors::VagrantError => e
raise Errors::ProviderNotUsable,
machine: name.to_s,
provider: provider.to_s,
message: e.to_s
end
end
# Add the sub-machine configuration to the loader and keys
@ -153,7 +163,7 @@ module Vagrant
local_keys = keys.dup
# Load the box Vagrantfile, if there is one
if config.vm.box
if config.vm.box && boxes
box = boxes.find(config.vm.box, box_formats, config.vm.box_version)
if box
box_vagrantfile = find_vagrantfile(box.directory)

View File

@ -38,6 +38,10 @@ module VagrantPlugins
options[:client_cert] = c
end
o.on("--location-trusted", "Trust 'Location' header from HTTP redirects and use the same credentials for subsequent urls as for the initial one") do |l|
options[:location_trusted] = l
end
o.on("--provider PROVIDER", String, "Provider the box should satisfy") do |p|
options[:provider] = p
end
@ -47,7 +51,7 @@ module VagrantPlugins
end
o.separator ""
o.separator "The box descriptor can be the name of a box on Vagrant Cloud,"
o.separator "The box descriptor can be the name of a box on HashiCorp's Atlas,"
o.separator "or a URL, or a local .box file, or a local .json file containing"
o.separator "the catalog metadata."
o.separator ""
@ -95,6 +99,7 @@ module VagrantPlugins
box_download_ca_path: options[:ca_path],
box_download_client_cert: options[:client_cert],
box_download_insecure: options[:insecure],
box_download_location_trusted: options[:location_trusted],
ui: Vagrant::UI::Prefixed.new(@env.ui, "box"),
})

View File

@ -32,7 +32,7 @@ module VagrantPlugins
:destroy, force_confirm_destroy: options[:force])
total += 1
declined += 1 if action_env.has_key?(:force_confirm_destroy_result) &&
declined += 1 if action_env.key?(:force_confirm_destroy_result) &&
action_env[:force_confirm_destroy_result] == false
end

View File

@ -0,0 +1,117 @@
require "rest_client"
module VagrantPlugins
module LoginCommand
class Client
# Initializes a login client with the given Vagrant::Environment.
#
# @param [Vagrant::Environment] env
def initialize(env)
@logger = Log4r::Logger.new("vagrant::login::client")
@env = env
end
# Removes the token, effectively logging the user out.
def clear_token
@logger.info("Clearing token")
token_path.delete if token_path.file?
end
# Checks if the user is logged in by verifying their authentication
# token.
#
# @return [Boolean]
def logged_in?
token = self.token
return false if !token
with_error_handling do
url = "#{Vagrant.server_url}/api/v1/authenticate" +
"?access_token=#{token}"
RestClient.get(url, content_type: :json)
true
end
end
# Login logs a user in and returns the token for that user. The token
# is _not_ stored unless {#store_token} is called.
#
# @param [String] user
# @param [String] pass
# @return [String] token The access token, or nil if auth failed.
def login(user, pass)
@logger.info("Logging in '#{user}'")
with_error_handling do
url = "#{Vagrant.server_url}/api/v1/authenticate"
request = { "user" => { "login" => user, "password" => pass } }
response = RestClient.post(
url, JSON.dump(request), content_type: :json)
data = JSON.load(response.to_s)
data["token"]
end
end
# Stores the given token locally, removing any previous tokens.
#
# @param [String] token
def store_token(token)
@logger.info("Storing token in #{token_path}")
token_path.open("w") do |f|
f.write(token)
end
nil
end
# Reads the access token if there is one. This will first read the
# `ATLAS_TOKEN` environment variable and then fallback to the stored
# access token on disk.
#
# @return [String]
def token
if ENV["ATLAS_TOKEN"] && !ENV["ATLAS_TOKEN"].empty?
@logger.debug("Using authentication token from environment variable")
return ENV["ATLAS_TOKEN"]
end
if token_path.exist?
@logger.debug("Using authentication token from disk at #{token_path}")
return token_path.read.strip
end
@logger.debug("No authentication token in environment or #{token_path}")
nil
end
protected
def with_error_handling(&block)
yield
rescue RestClient::Unauthorized
@logger.debug("Unauthorized!")
false
rescue RestClient::NotAcceptable => e
@logger.debug("Got unacceptable response:")
@logger.debug(e.message)
@logger.debug(e.backtrace.join("\n"))
begin
errors = JSON.parse(e.response)["errors"].join("\n")
raise Errors::ServerError, errors: errors
rescue JSON::ParserError; end
raise "An unexpected error occurred: #{e.inspect}"
rescue SocketError
@logger.info("Socket error")
raise Errors::ServerUnreachable, url: Vagrant.server_url.to_s
end
def token_path
@env.data_dir.join("vagrant_login_token")
end
end
end
end

View File

@ -0,0 +1,102 @@
module VagrantPlugins
module LoginCommand
class Command < Vagrant.plugin("2", "command")
def self.synopsis
"log in to HashiCorp's Atlas"
end
def execute
options = {}
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant login"
o.separator ""
o.on("-c", "--check", "Only checks if you're logged in") do |c|
options[:check] = c
end
o.on("-k", "--logout", "Logs you out if you're logged in") do |k|
options[:logout] = k
end
o.on("-t", "--token TOKEN", String, "Set the Atlas token") do |t|
options[:token] = t
end
end
# Parse the options
argv = parse_options(opts)
return if !argv
@client = Client.new(@env)
# Determine what task we're actually taking based on flags
if options[:check]
return execute_check
elsif options[:logout]
return execute_logout
elsif options[:token]
return execute_token(options[:token])
end
# Let the user know what is going on.
@env.ui.output(I18n.t("login_command.command_header") + "\n")
# If it is a private cloud installation, show that
if Vagrant.server_url != Vagrant::DEFAULT_SERVER_URL
@env.ui.output("Atlas URL: #{Vagrant.server_url}")
end
# Ask for the username
login = nil
password = nil
while !login
login = @env.ui.ask("Atlas Username: ")
end
while !password
password = @env.ui.ask("Password (will be hidden): ", echo: false)
end
token = @client.login(login, password)
if !token
@env.ui.error(I18n.t("login_command.invalid_login"))
return 1
end
@client.store_token(token)
@env.ui.success(I18n.t("login_command.logged_in"))
0
end
def execute_check
if @client.logged_in?
@env.ui.success(I18n.t("login_command.check_logged_in"))
return 0
else
@env.ui.error(I18n.t("login_command.check_not_logged_in"))
return 1
end
end
def execute_logout
@client.clear_token
@env.ui.success(I18n.t("login_command.logged_out"))
return 0
end
def execute_token(token)
@client.store_token(token)
@env.ui.success(I18n.t("login_command.token_saved"))
if @client.logged_in?
@env.ui.success(I18n.t("login_command.check_logged_in"))
return 0
else
@env.ui.error(I18n.t("login_command.invalid_token"))
return 1
end
end
end
end
end

View File

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

View File

@ -0,0 +1,34 @@
en:
login_command:
errors:
server_error: |-
The Atlas server responded with an not-OK response:
%{errors}
server_unreachable: |-
The Atlas server is not currently accepting connections. Please check
your network connection and try again later.
check_logged_in: |-
You are already logged in.
check_not_logged_in: |-
You are not currently logged in. Please run `vagrant login` and provide
your login information to authenticate.
command_header: |-
In a moment we will ask for your username and password to HashiCorp's
Atlas. After authenticating, we will store an access token locally on
disk. Your login details will be transmitted over a secure connection, and
are never stored on disk locally.
If you do not have an Atlas account, sign up at
https://atlas.hashicorp.com.
invalid_login: |-
Invalid username or password. Please try again.
invalid_token: |-
Invalid token. Please try again.
logged_in: |-
You are now logged in.
logged_out: |-
You are logged out.
token_saved: |-
The token was successfully saved.

View File

@ -0,0 +1,45 @@
require "uri"
require_relative "../client"
module VagrantPlugins
module LoginCommand
class AddAuthentication
def initialize(app, env)
@app = app
end
def call(env)
client = Client.new(env[:env])
token = client.token
if token && Vagrant.server_url
server_uri = URI.parse(Vagrant.server_url)
env[:box_urls].map! do |url|
u = URI.parse(url)
replace = u.host == server_uri.host
if !replace
# We need this in here for the transition we made from
# Vagrant Cloud to Atlas. This preserves access tokens
# appending to both without leaking access tokens to
# unsavory URLs.
replace = u.host == "vagrantcloud.com" &&
server_uri.host == "atlas.hashicorp.com"
end
if replace
u.query ||= ""
u.query += "&" if u.query != ""
u.query += "access_token=#{token}"
end
u.to_s
end
end
@app.call(env)
end
end
end
end

View File

@ -0,0 +1,35 @@
require "vagrant"
module VagrantPlugins
module LoginCommand
autoload :Client, File.expand_path("../client", __FILE__)
autoload :Errors, File.expand_path("../errors", __FILE__)
class Plugin < Vagrant.plugin("2")
name "vagrant-login"
description <<-DESC
Provides the login command and internal API access to Atlas.
DESC
command(:login) do
require_relative "command"
init!
Command
end
action_hook(:cloud_authenticated_boxes, :authenticate_box_url) do |hook|
require_relative "middleware/add_authentication"
hook.prepend(AddAuthentication)
end
protected
def self.init!
return if defined?(@_init)
I18n.load_path << File.expand_path("../locales/en.yml", __FILE__)
I18n.reload!
@_init = true
end
end
end
end

View File

@ -34,20 +34,29 @@ module VagrantPlugins
system = ", system" if plugin && plugin["system"]
env[:ui].info "#{spec.name} (#{spec.version}#{system})"
env[:ui].machine("plugin-name", spec.name)
env[:ui].machine("plugin-version", "#{spec.version}#{system}")
env[:ui].machine(
"plugin-version",
"#{spec.version}#{system}",
target: spec.name)
if plugin["gem_version"] && plugin["gem_version"] != ""
env[:ui].info(I18n.t(
"vagrant.commands.plugin.plugin_version",
version: plugin["gem_version"]))
env[:ui].machine("plugin-version-constraint", plugin["gem_version"])
env[:ui].machine(
"plugin-version-constraint",
plugin["gem_version"],
target: spec.name)
end
if plugin["require"] && plugin["require"] != ""
env[:ui].info(I18n.t(
"vagrant.commands.plugin.plugin_require",
require: plugin["require"]))
env[:ui].machine("plugin-custom-entrypoint", plugin["require"])
env[:ui].machine(
"plugin-custom-entrypoint",
plugin["require"],
target: spec.name)
end
end

View File

@ -12,7 +12,7 @@ module VagrantPlugins
def call(env)
installed = Vagrant::Plugin::Manager.instance.installed_plugins
if !installed.has_key?(env[:plugin_name])
if !installed.key?(env[:plugin_name])
raise Vagrant::Errors::PluginNotInstalled,
name: env[:plugin_name]
end

View File

@ -0,0 +1,75 @@
require 'optparse'
module VagrantPlugins
module CommandPush
class Command < Vagrant.plugin("2", :command)
def self.synopsis
"deploys code in this environment to a configured destination"
end
# @todo support multiple strategies if requested by the community
def execute
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant push [strategy] [options]"
end
# Parse the options
argv = parse_options(opts)
return if !argv
name = validate_pushes!(@env.pushes, argv[0])
# Validate the configuration
@env.machine(@env.machine_names.first, @env.default_provider).action_raw(
:config_validate,
Vagrant::Action::Builtin::ConfigValidate)
@logger.debug("'push' environment with strategy: `#{name}'")
@env.push(name)
0
end
# Validate that the given list of names corresponds to valid pushes.
#
# @raise Vagrant::Errors::PushesNotDefined
# if there are no pushes defined
# @raise Vagrant::Errors::PushStrategyNotProvided
# if there are multiple push strategies defined and none were specified
# @raise Vagrant::Errors::PushStrategyNotDefined
# if the given push name do not correspond to a push strategy
#
# @param [Array<Symbol>] pushes
# the list of pushes defined by the environment
# @param [String] name
# the name provided by the user on the command line
#
# @return [Symbol]
# the compiled list of pushes
#
def validate_pushes!(pushes, name = nil)
if pushes.nil? || pushes.empty?
raise Vagrant::Errors::PushesNotDefined
end
if name.nil?
if pushes.length == 1
return pushes.first.to_sym
else
raise Vagrant::Errors::PushStrategyNotProvided,
pushes: pushes.map(&:to_s)
end
end
name = name.to_sym
if !pushes.include?(name)
raise Vagrant::Errors::PushStrategyNotDefined,
name: name.to_s,
pushes: pushes.map(&:to_s)
end
return name
end
end
end
end

View File

@ -0,0 +1,17 @@
require "vagrant"
module VagrantPlugins
module CommandPush
class Plugin < Vagrant.plugin("2")
name "push command"
description <<-DESC
The `push` command deploys code in this environment.
DESC
command("push") do
require File.expand_path("../command", __FILE__)
Command
end
end
end
end

View File

@ -70,8 +70,17 @@ module VagrantPlugins
rdp_info[:username] = username
end
if !rdp_info[:password]
password = ssh_info[:password]
if machine.config.vm.communicator == :winrm
password = machine.config.winrm.password
end
rdp_info[:password] = password
end
rdp_info[:host] ||= ssh_info[:host]
rdp_info[:port] ||= machine.config.rdp.port
rdp_info[:username] ||= machine.config.rdp.username
if rdp_info[:host] == "127.0.0.1"
# We need to find a forwarded port...

View File

@ -3,15 +3,18 @@ module VagrantPlugins
class Config < Vagrant.plugin("2", :config)
attr_accessor :port
attr_accessor :search_port
attr_accessor :username
def initialize
@port = UNSET_VALUE
@search_port = UNSET_VALUE
@username = UNSET_VALUE
end
def finalize!
@port = 3389 if @port == UNSET_VALUE
@search_port = 3389 if @search_port == UNSET_VALUE
@username = nil if @username == UNSET_VALUE
end
def validate(machine)

View File

@ -40,7 +40,8 @@ module VagrantPlugins
private_key_path: ssh_info[:private_key_path],
forward_agent: ssh_info[:forward_agent],
forward_x11: ssh_info[:forward_x11],
proxy_command: ssh_info[:proxy_command]
proxy_command: ssh_info[:proxy_command],
ssh_command: ssh_info[:ssh_command]
}
# Render the template and output directly to STDOUT

View File

@ -58,24 +58,37 @@ module VagrantPlugins
@env.batch(options[:parallel]) do |batch|
names = argv
if names.empty?
autostart = false
@env.vagrantfile.machine_names_and_options.each do |n, o|
o[:autostart] = true if !o.has_key?(:autostart)
autostart = true if o.key?(:autostart)
o[:autostart] = true if !o.key?(:autostart)
names << n.to_s if o[:autostart]
end
# If we have an autostart key but no names, it means that
# all machines are autostart: false and we don't start anything.
names = nil if autostart && names.empty?
end
with_target_vms(names, provider: options[:provider]) do |machine|
@env.ui.info(I18n.t(
"vagrant.commands.up.upping",
name: machine.name,
provider: machine.provider_name))
if names
with_target_vms(names, provider: options[:provider]) do |machine|
@env.ui.info(I18n.t(
"vagrant.commands.up.upping",
name: machine.name,
provider: machine.provider_name))
machines << machine
machines << machine
batch.action(machine, :up, options)
batch.action(machine, :up, options)
end
end
end
if machines.empty?
@env.ui.info(I18n.t("vagrant.up_no_machines"))
return 0
end
# Output the post-up messages that we have, if any
machines.each do |m|
next if !m.config.vm.post_up_message

View File

@ -19,6 +19,8 @@ module VagrantPlugins
parser.on("--provision-with x,y,z", Array,
"Enable only certain provisioners, by type.") do |list|
options[:provision_types] = list.map { |type| type.to_sym }
options[:provision_enabled] = true
options[:provision_ignore_sentinel] = true
end
end

View File

@ -11,9 +11,9 @@ require 'net/scp'
require 'vagrant/util/ansi_escape_code_remover'
require 'vagrant/util/file_mode'
require 'vagrant/util/keypair'
require 'vagrant/util/platform'
require 'vagrant/util/retryable'
require 'vagrant/util/ssh'
module VagrantPlugins
module CommunicatorSSH
@ -85,6 +85,12 @@ module VagrantPlugins
raise
rescue Vagrant::Errors::SSHKeyTypeNotSupported
raise
rescue Vagrant::Errors::SSHKeyBadOwner
raise
rescue Vagrant::Errors::SSHKeyBadPermissions
raise
rescue Vagrant::Errors::SSHInsertKeyUnsupported
raise
rescue Vagrant::Errors::VagrantError => e
# Ignore it, SSH is not ready, some other error.
end
@ -139,20 +145,41 @@ module VagrantPlugins
# If we used a password, then insert the insecure key
ssh_info = @machine.ssh_info
if ssh_info[:password] && ssh_info[:private_key_path].empty?
@logger.info("Inserting insecure key to avoid password")
@machine.ui.info(I18n.t("vagrant.inserting_insecure_key"))
@machine.guest.capability(
:insert_public_key,
Vagrant.source_root.join("keys", "vagrant.pub").read.chomp)
insert = ssh_info[:password] && ssh_info[:private_key_path].empty?
ssh_info[:private_key_path].each do |pk|
if insecure_key?(pk)
insert = true
@machine.ui.detail("\n"+I18n.t("vagrant.inserting_insecure_detected"))
break
end
end
if insert
# If we don't have the power to insert/remove keys, then its an error
cap = @machine.guest.capability?(:insert_public_key) &&
@machine.guest.capability?(:remove_public_key)
raise Vagrant::Errors::SSHInsertKeyUnsupported if !cap
_pub, priv, openssh = Vagrant::Util::Keypair.create
@logger.info("Inserting key to avoid password: #{openssh}")
@machine.ui.detail("\n"+I18n.t("vagrant.inserting_random_key"))
@machine.guest.capability(:insert_public_key, openssh)
# Write out the private key in the data dir so that the
# machine automatically picks it up.
@machine.data_dir.join("private_key").open("w+") do |f|
f.write(Vagrant.source_root.join("keys", "vagrant").read)
f.write(priv)
end
@machine.ui.info(I18n.t("vagrant.inserted_key"))
# Remove the old key if it exists
@machine.ui.detail(I18n.t("vagrant.inserting_remove_key"))
@machine.guest.capability(
:remove_public_key,
Vagrant.source_root.join("keys", "vagrant.pub").read.chomp)
# Done, restart.
@machine.ui.detail(I18n.t("vagrant.inserted_key"))
@connection.close
@connection = nil
@ -265,8 +292,13 @@ module VagrantPlugins
# There is a chance that the socket is closed despite us checking
# 'closed?' above. To test this we need to send data through the
# socket.
#
# We wrap the check itself in a 5 second timeout because there
# are some cases where this will just hang.
begin
@connection.exec!("")
Timeout.timeout(5) do
@connection.exec!("")
end
rescue Exception => e
@logger.info("Connection errored, not re-using. Will reconnect.")
@logger.debug(e.inspect)
@ -288,11 +320,17 @@ module VagrantPlugins
raise Vagrant::Errors::SSHNotReady if ssh_info.nil?
# Default some options
opts[:retries] = 5 if !opts.has_key?(:retries)
opts[:retries] = 5 if !opts.key?(:retries)
# Set some valid auth methods. We disable the auth methods that
# we're not using if we don't have the right auth info.
auth_methods = ["none", "hostbased"]
auth_methods << "publickey" if ssh_info[:private_key_path]
auth_methods << "password" if ssh_info[:password]
# Build the options we'll use to initiate the connection via Net::SSH
common_connect_opts = {
auth_methods: ["none", "publickey", "hostbased", "password"],
auth_methods: auth_methods,
config: false,
forward_agent: ssh_info[:forward_agent],
keys: ssh_info[:private_key_path],
@ -305,11 +343,6 @@ module VagrantPlugins
verbose: :debug,
}
# Check that the private key permissions are valid
ssh_info[:private_key_path].each do |path|
Vagrant::Util::SSH.check_key_permissions(Pathname.new(path))
end
# Connect to SSH, giving it a few tries
connection = nil
begin
@ -407,6 +440,21 @@ module VagrantPlugins
return yield connection if block_given?
end
# The shell wrapper command used in shell_execute defined by
# the sudo and shell options.
def shell_cmd(opts)
sudo = opts[:sudo]
shell = opts[:shell]
# Determine the shell to execute. Prefer the explicitly passed in shell
# over the default configured shell. If we are using `sudo` then we
# need to wrap the shell in a `sudo` call.
cmd = @machine.config.ssh.shell
cmd = shell if shell
cmd = @machine.config.ssh.sudo_command.gsub("%c", cmd) if sudo
cmd
end
# Executes the command on an SSH connection within a login shell.
def shell_execute(connection, command, **opts)
opts = {
@ -415,18 +463,10 @@ module VagrantPlugins
}.merge(opts)
sudo = opts[:sudo]
shell = opts[:shell]
@logger.info("Execute: #{command} (sudo=#{sudo.inspect})")
exit_status = nil
# Determine the shell to execute. Prefer the explicitly passed in shell
# over the default configured shell. If we are using `sudo` then we
# need to wrap the shell in a `sudo` call.
shell_cmd = @machine.config.ssh.shell
shell_cmd = shell if shell
shell_cmd = "sudo -E -H #{shell_cmd}" if sudo
# These variables are used to scrub PTY output if we're in a PTY
pty = false
pty_stdout = ""
@ -445,7 +485,7 @@ module VagrantPlugins
end
end
ch.exec(shell_cmd) do |ch2, _|
ch.exec(shell_cmd(opts)) do |ch2, _|
# Setup the channel callbacks so we can get data and exit status
ch2.on_data do |ch3, data|
# Filter out the clear screen command
@ -594,6 +634,16 @@ module VagrantPlugins
# Otherwise, just raise the error up
raise
end
# This will test whether path is the Vagrant insecure private key.
#
# @param [String] path
def insecure_key?(path)
return false if !path
return false if !File.file?(path)
source_path = Vagrant.source_root.join("keys", "vagrant")
return File.read(path).chomp == source_path.read.chomp
end
end
end
end

0
plugins/communicators/winrm/command_filters/mkdir.rb Executable file → Normal file
View File

View File

@ -20,7 +20,7 @@ module VagrantPlugins
break
end
end
# Figure out which argument is the path
dir = cmd_parts.pop
while !dir.nil? && dir.start_with?('-')

View File

@ -25,22 +25,89 @@ module VagrantPlugins
@logger.info("Initializing WinRMCommunicator")
end
def wait_for_ready(timeout)
Timeout.timeout(timeout) do
# Wait for winrm_info to be ready
winrm_info = nil
while true
winrm_info = Helper.winrm_info(@machine)
break if winrm_info
sleep 0.5
end
# Got it! Let the user know what we're connecting to.
@machine.ui.detail("WinRM address: #{shell.host}:#{shell.port}")
@machine.ui.detail("WinRM username: #{shell.username}")
@machine.ui.detail("WinRM transport: #{shell.config.transport}")
last_message = nil
last_message_repeat_at = 0
while true
message = nil
begin
begin
return true if ready?
rescue Vagrant::Errors::VagrantError => e
@logger.info("WinRM not ready: #{e.inspect}")
raise
end
rescue Errors::ConnectionTimeout
message = "Connection timeout."
rescue Errors::AuthenticationFailed
message = "Authentication failure."
rescue Errors::Disconnected
message = "Remote connection disconnect."
rescue Errors::ConnectionRefused
message = "Connection refused."
rescue Errors::ConnectionReset
message = "Connection reset."
rescue Errors::HostDown
message = "Host appears down."
rescue Errors::NoRoute
message = "Host unreachable."
rescue Errors::TransientError => e
# Any other retriable errors
message = e.message
end
# If we have a message to show, then show it. We don't show
# repeated messages unless they've been repeating longer than
# 10 seconds.
if message
message_at = Time.now.to_f
show_message = true
if last_message == message
show_message = (message_at - last_message_repeat_at) > 10.0
end
if show_message
@machine.ui.detail("Warning: #{message} Retrying...")
last_message = message
last_message_repeat_at = message_at
end
end
end
end
rescue Timeout::Error
return false
end
def ready?
@logger.info("Checking whether WinRM is ready...")
Timeout.timeout(@machine.config.winrm.timeout) do
result = Timeout.timeout(@machine.config.winrm.timeout) do
shell(true).powershell("hostname")
end
@logger.info("WinRM is ready!")
return true
rescue Vagrant::Errors::VagrantError => e
# We catch a `VagrantError` which would signal that something went
# wrong expectedly in the `connect`, which means we didn't connect.
rescue Errors::TransientError => e
# We catch a `TransientError` which would signal that something went
# that might work if we wait and retry.
@logger.info("WinRM not up: #{e.inspect}")
# We reset the shell to trigger calling of winrm_finder again.
# This resolves a problem when using vSphere where the ssh_info was not refreshing
# This resolves a problem when using vSphere where the winrm_info was not refreshing
# thus never getting the correct hostname.
@shell = nil
return false
@ -67,24 +134,28 @@ module VagrantPlugins
}.merge(opts || {})
opts[:good_exit] = Array(opts[:good_exit])
if opts[:elevated]
guest_script_path = create_elevated_shell_script(command)
command = "powershell -executionpolicy bypass -file #{guest_script_path}"
end
command = wrap_in_scheduled_task(command) if opts[:elevated]
output = shell.send(opts[:shell], command, &block)
execution_output(output, opts)
end
alias_method :sudo, :execute
def test(command, opts=nil)
# If this is a *nix command with no Windows equivilant, assume failure
# If this is a *nix command (which we know about) with no Windows
# equivilant, assume failure
command = @cmd_filter.filter(command)
return false if command.empty?
opts = { error_check: false }.merge(opts || {})
execute(command, opts) == 0
opts = {
command: command,
elevated: false,
error_check: false,
}.merge(opts || {})
# If we're passed a *nix command which PS can't parse we get exit code
# 0, but output in stderr. We need to check both exit code and stderr.
output = shell.send(:powershell, command)
return output[:exitcode] == 0 && flatten_stderr(output).length == 0
end
def upload(from, to)
@ -106,27 +177,20 @@ module VagrantPlugins
WinRMShell.new(
winrm_info[:host],
@machine.config.winrm.username,
@machine.config.winrm.password,
port: winrm_info[:port],
timeout_in_seconds: @machine.config.winrm.timeout,
max_tries: @machine.config.winrm.max_tries,
winrm_info[:port],
@machine.config.winrm
)
end
# Creates and uploads a PowerShell script which wraps the specified
# command in a scheduled task. The scheduled task allows commands to
# run on the guest as a true local admin without any of the restrictions
# that WinRM puts in place.
# Creates and uploads a PowerShell script which wraps a command in a
# scheduled task. The scheduled task allows commands to run on the guest
# as a true local admin without any of the restrictions that WinRM puts
# in place.
#
# @return The path to elevated_shell.ps1 on the guest
def create_elevated_shell_script(command)
# @return The wrapper command to execute
def wrap_in_scheduled_task(command)
path = File.expand_path("../scripts/elevated_shell.ps1", __FILE__)
script = Vagrant::Util::TemplateRenderer.render(path, options: {
username: shell.username,
password: shell.password,
command: command.gsub("\"", "`\""),
})
script = Vagrant::Util::TemplateRenderer.render(path)
guest_script_path = "c:/tmp/vagrant-elevated-shell.ps1"
file = Tempfile.new(["vagrant-elevated-shell", "ps1"])
begin
@ -138,7 +202,15 @@ module VagrantPlugins
file.close
file.unlink
end
guest_script_path
# convert to double byte unicode string then base64 encode
# just like PowerShell -EncodedCommand expects
wrapped_encoded_command = Base64.strict_encode64(
"#{command}; exit $LASTEXITCODE".encode('UTF-16LE', 'UTF-8'))
"powershell -executionpolicy bypass -file \"#{guest_script_path}\" " +
"-username \"#{shell.username}\" -password \"#{shell.password}\" " +
"-encoded_command \"#{wrapped_encoded_command}\""
end
# Handles the raw WinRM shell result and converts it to a
@ -156,8 +228,8 @@ module VagrantPlugins
def raise_execution_error(output, opts)
# WinRM can return multiple stderr and stdout entries
error_opts = opts.merge(
stdout: output[:data].collect { |e| e[:stdout] }.join,
stderr: output[:data].collect { |e| e[:stderr] }.join
stdout: flatten_stdout(output),
stderr: flatten_stderr(output)
)
# Use a different error message key if the caller gave us one,
@ -167,6 +239,20 @@ module VagrantPlugins
# Raise the error, use the type the caller gave us or the comm default
raise opts[:error_class], error_opts
end
# TODO: Replace with WinRM Output class when WinRM 1.3 is released
def flatten_stderr(output)
output[:data].map do | line |
line[:stderr]
end.compact.join
end
def flatten_stdout(output)
output[:data].map do | line |
line[:flatten_stdout]
end.compact.join
end
end #WinRM class
end
end

View File

@ -7,37 +7,51 @@ module VagrantPlugins
attr_accessor :port
attr_accessor :guest_port
attr_accessor :max_tries
attr_accessor :retry_delay
attr_accessor :timeout
attr_accessor :transport
attr_accessor :ssl_peer_verification
def initialize
@username = UNSET_VALUE
@password = UNSET_VALUE
@host = UNSET_VALUE
@port = UNSET_VALUE
@guest_port = UNSET_VALUE
@max_tries = UNSET_VALUE
@timeout = UNSET_VALUE
@username = UNSET_VALUE
@password = UNSET_VALUE
@host = UNSET_VALUE
@port = UNSET_VALUE
@guest_port = UNSET_VALUE
@max_tries = UNSET_VALUE
@retry_delay = UNSET_VALUE
@timeout = UNSET_VALUE
@transport = UNSET_VALUE
@ssl_peer_verification = UNSET_VALUE
end
def finalize!
@username = "vagrant" if @username == UNSET_VALUE
@password = "vagrant" if @password == UNSET_VALUE
@username = "vagrant" if @username == UNSET_VALUE
@password = "vagrant" if @password == UNSET_VALUE
@transport = :plaintext if @transport == UNSET_VALUE
@host = nil if @host == UNSET_VALUE
@port = 5985 if @port == UNSET_VALUE
@guest_port = 5985 if @guest_port == UNSET_VALUE
@max_tries = 20 if @max_tries == UNSET_VALUE
@timeout = 1800 if @timeout == UNSET_VALUE
is_ssl = @transport == :ssl
@port = (is_ssl ? 5986 : 5985) if @port == UNSET_VALUE
@guest_port = (is_ssl ? 5986 : 5985) if @guest_port == UNSET_VALUE
@max_tries = 20 if @max_tries == UNSET_VALUE
@retry_delay = 2 if @retry_delay == UNSET_VALUE
@timeout = 1800 if @timeout == UNSET_VALUE
@ssl_peer_verification = true if @ssl_peer_verification == UNSET_VALUE
end
def validate(machine)
errors = []
errors << "winrm.username cannot be nil." if @username.nil?
errors << "winrm.password cannot be nil." if @password.nil?
errors << "winrm.port cannot be nil." if @port.nil?
errors << "winrm.guest_port cannot be nil." if @guest_port.nil?
errors << "winrm.max_tries cannot be nil." if @max_tries.nil?
errors << "winrm.timeout cannot be nil." if @timeout.nil?
errors << "winrm.username cannot be nil." if @username.nil?
errors << "winrm.password cannot be nil." if @password.nil?
errors << "winrm.port cannot be nil." if @port.nil?
errors << "winrm.guest_port cannot be nil." if @guest_port.nil?
errors << "winrm.max_tries cannot be nil." if @max_tries.nil?
errors << "winrm.retry_delay cannot be nil." if @max_tries.nil?
errors << "winrm.timeout cannot be nil." if @timeout.nil?
unless @ssl_peer_verification == true || @ssl_peer_verification == false
errors << "winrm.ssl_peer_verification must be a boolean."
end
{ "WinRM" => errors }
end

View File

@ -6,8 +6,11 @@ module VagrantPlugins
error_namespace("vagrant_winrm.errors")
end
class AuthError < WinRMError
error_key(:auth_error)
class TransientError < WinRMError
end
class AuthenticationFailed < WinRMError
error_key(:authentication_failed)
end
class ExecutionError < WinRMError
@ -29,6 +32,38 @@ module VagrantPlugins
class WinRMFileTransferError < WinRMError
error_key(:winrm_file_transfer_error)
end
class InvalidTransport < WinRMError
error_key(:invalid_transport)
end
class SSLError < WinRMError
error_key(:ssl_error)
end
class ConnectionTimeout < TransientError
error_key(:connection_timeout)
end
class Disconnected < TransientError
error_key(:disconnected)
end
class ConnectionRefused < TransientError
error_key(:connection_refused)
end
class ConnectionReset < TransientError
error_key(:connection_reset)
end
class HostDown < TransientError
error_key(:host_down)
end
class NoRoute < TransientError
error_key(:no_route)
end
end
end
end

View File

@ -1,163 +0,0 @@
require "log4r"
module VagrantPlugins
module CommunicatorWinRM
# Manages the file system on the remote guest allowing for file tranfer
# between the guest and host.
class FileManager
def initialize(shell)
@logger = Log4r::Logger.new("vagrant::communication::filemanager")
@shell = shell
end
# Uploads the given file or directory from the host to the guest (recursively).
#
# @param [String] The source file or directory path on the host
# @param [String] The destination file or directory path on the host
def upload(host_src_file_path, guest_dest_file_path)
@logger.debug("Upload: #{host_src_file_path} -> #{guest_dest_file_path}")
if File.directory?(host_src_file_path)
upload_directory(host_src_file_path, guest_dest_file_path)
else
upload_file(host_src_file_path, guest_dest_file_path)
end
end
# Downloads the given file from the guest to the host.
# NOTE: This currently only supports single file download
#
# @param [String] The source file path on the guest
# @param [String] The destination file path on the host
def download(guest_src_file_path, host_dest_file_path)
@logger.debug("#{guest_src_file_path} -> #{host_dest_file_path}")
output = @shell.powershell("[System.convert]::ToBase64String([System.IO.File]::ReadAllBytes(\"#{guest_src_file_path}\"))")
contents = output[:data].map!{|line| line[:stdout]}.join.gsub("\\n\\r", '')
out = Base64.decode64(contents)
IO.binwrite(host_dest_file_path, out)
end
private
# Recursively uploads the given directory from the host to the guest
#
# @param [String] The source file or directory path on the host
# @param [String] The destination file or directory path on the host
def upload_directory(host_src_file_path, guest_dest_file_path)
glob_patt = File.join(host_src_file_path, '**/*')
Dir.glob(glob_patt).select { |f| !File.directory?(f) }.each do |host_file_path|
guest_file_path = guest_file_path(host_src_file_path, guest_dest_file_path, host_file_path)
upload_file(host_file_path, guest_file_path)
end
end
# Uploads the given file, but only if the target file doesn't exist
# or its MD5 checksum doens't match the host's source checksum.
#
# @param [String] The source file path on the host
# @param [String] The destination file path on the guest
def upload_file(host_src_file_path, guest_dest_file_path)
if should_upload_file?(host_src_file_path, guest_dest_file_path)
tmp_file_path = upload_to_temp_file(host_src_file_path)
decode_temp_file(tmp_file_path, guest_dest_file_path)
else
@logger.debug("Up to date: #{guest_dest_file_path}")
end
end
# Uploads the given file to a new temp file on the guest
#
# @param [String] The source file path on the host
# @return [String] The temp file path on the guest
def upload_to_temp_file(host_src_file_path)
tmp_file_path = File.join(guest_temp_dir, "winrm-upload-#{rand()}")
@logger.debug("Uploading '#{host_src_file_path}' to temp file '#{tmp_file_path}'")
base64_host_file = Base64.encode64(IO.binread(host_src_file_path)).gsub("\n",'')
base64_host_file.chars.to_a.each_slice(8000-tmp_file_path.size) do |chunk|
out = @shell.cmd("echo #{chunk.join} >> \"#{tmp_file_path}\"")
raise_upload_error_if_failed(out, host_src_file_path, tmp_file_path)
end
tmp_file_path
end
# Moves and decodes the given file temp file on the guest to its
# permanent location
#
# @param [String] The source base64 encoded temp file path on the guest
# @param [String] The destination file path on the guest
def decode_temp_file(guest_tmp_file_path, guest_dest_file_path)
@logger.debug("Decoding temp file '#{guest_tmp_file_path}' to '#{guest_dest_file_path}'")
out = @shell.powershell <<-EOH
$tmp_file_path = [System.IO.Path]::GetFullPath('#{guest_tmp_file_path}')
$dest_file_path = [System.IO.Path]::GetFullPath('#{guest_dest_file_path}')
if (Test-Path $dest_file_path) {
rm $dest_file_path
}
else {
$dest_dir = ([System.IO.Path]::GetDirectoryName($dest_file_path))
New-Item -ItemType directory -Force -Path $dest_dir
}
$base64_string = Get-Content $tmp_file_path
$bytes = [System.Convert]::FromBase64String($base64_string)
[System.IO.File]::WriteAllBytes($dest_file_path, $bytes)
EOH
raise_upload_error_if_failed(out, guest_tmp_file_path, guest_dest_file_path)
end
# Checks to see if the target file on the guest is missing or out of date.
#
# @param [String] The source file path on the host
# @param [String] The destination file path on the guest
# @return [Boolean] True if the file is missing or out of date
def should_upload_file?(host_src_file_path, guest_dest_file_path)
local_md5 = Digest::MD5.file(host_src_file_path).hexdigest
cmd = <<-EOH
$dest_file_path = [System.IO.Path]::GetFullPath('#{guest_dest_file_path}')
if (Test-Path $dest_file_path) {
$crypto_provider = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
try {
$file = [System.IO.File]::Open($dest_file_path, [System.IO.Filemode]::Open, [System.IO.FileAccess]::Read)
$guest_md5 = ([System.BitConverter]::ToString($crypto_provider.ComputeHash($file))).Replace("-","").ToLower()
}
finally {
$file.Dispose()
}
if ($guest_md5 -eq '#{local_md5}') {
exit 0
}
}
Write-Host "should upload file $dest_file_path"
exit 1
EOH
@shell.powershell(cmd)[:exitcode] == 1
end
# Creates a guest file path equivalent from a host file path
#
# @param [String] The base host directory we're going to copy from
# @param [String] The base guest directory we're going to copy to
# @param [String] A full path to a file on the host underneath host_base_dir
# @return [String] The guest file path equivalent
def guest_file_path(host_base_dir, guest_base_dir, host_file_path)
rel_path = File.dirname(host_file_path[host_base_dir.length, host_file_path.length])
File.join(guest_base_dir, rel_path, File.basename(host_file_path))
end
def guest_temp_dir
@guest_temp ||= (@shell.cmd('echo %TEMP%'))[:data][0][:stdout].chomp
end
def raise_upload_error_if_failed(out, from, to)
raise Errors::WinRMFileTransferError,
from: from,
to: to,
message: out.inspect if out[:exitcode] != 0
end
end
end
end

View File

@ -1,6 +1,4 @@
$command = "<%= options[:command] %>" + '; exit $LASTEXITCODE'
$user = '<%= options[:username] %>'
$password = '<%= options[:password] %>'
param([String]$username, [String]$password, [String]$encoded_command)
$task_name = "WinRM_Elevated_Shell"
$out_file = "$env:SystemRoot\Temp\WinRM_Elevated_Shell.log"
@ -14,7 +12,7 @@ $task_xml = @'
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<Principals>
<Principal id="Author">
<UserId>{user}</UserId>
<UserId>{username}</UserId>
<LogonType>Password</LogonType>
<RunLevel>HighestAvailable</RunLevel>
</Principal>
@ -27,7 +25,7 @@ $task_xml = @'
<StartWhenAvailable>false</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>true</StopOnIdleEnd>
<StopOnIdleEnd>false</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
@ -47,19 +45,17 @@ $task_xml = @'
</Task>
'@
$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)
$encoded_command = [Convert]::ToBase64String($bytes)
$arguments = "/c powershell.exe -EncodedCommand $encoded_command &gt; $out_file 2&gt;&amp;1"
$task_xml = $task_xml.Replace("{arguments}", $arguments)
$task_xml = $task_xml.Replace("{user}", $user)
$task_xml = $task_xml.Replace("{username}", $username)
$schedule = New-Object -ComObject "Schedule.Service"
$schedule.Connect()
$task = $schedule.NewTask($null)
$task.XmlText = $task_xml
$folder = $schedule.GetFolder("\")
$folder.RegisterTaskDefinition($task_name, $task, 6, $user, $password, 1, $null) | Out-Null
$folder.RegisterTaskDefinition($task_name, $task, 6, $username, $password, 1, $null) | Out-Null
$registered_task = $folder.GetTask("\$task_name")
$registered_task.Run($null) | Out-Null

View File

@ -9,7 +9,7 @@ Vagrant::Util::SilenceWarnings.silence! do
require "winrm"
end
require_relative "file_manager"
require "winrm-fs/file_manager"
module VagrantPlugins
module CommunicatorWinRM
@ -22,6 +22,7 @@ module VagrantPlugins
@@exceptions_to_retry_on = [
HTTPClient::KeepAliveDisconnected,
WinRM::WinRMHTTPTransportError,
WinRM::WinRMAuthorizationError,
Errno::EACCES,
Errno::EADDRINUSE,
Errno::ECONNREFUSED,
@ -32,23 +33,21 @@ module VagrantPlugins
]
attr_reader :logger
attr_reader :username
attr_reader :password
attr_reader :host
attr_reader :port
attr_reader :timeout_in_seconds
attr_reader :max_tries
attr_reader :username
attr_reader :password
attr_reader :config
def initialize(host, username, password, options = {})
def initialize(host, port, config)
@logger = Log4r::Logger.new("vagrant::communication::winrmshell")
@logger.debug("initializing WinRMShell")
@host = host
@port = options[:port] || 5985
@username = username
@password = password
@timeout_in_seconds = options[:timeout_in_seconds] || 60
@max_tries = options[:max_tries] || 20
@host = host
@port = port
@username = config.username
@password = config.password
@config = config
end
def powershell(command, &block)
@ -67,11 +66,13 @@ module VagrantPlugins
end
def upload(from, to)
FileManager.new(self).upload(from, to)
file_manager = WinRM::FS::FileManager.new(session)
file_manager.upload(from, to)
end
def download(from, to)
FileManager.new(self).download(from, to)
file_manager = WinRM::FS::FileManager.new(session)
file_manager.download(from, to)
end
protected
@ -87,41 +88,80 @@ module VagrantPlugins
end
def execute_shell_with_retry(command, shell, &block)
retryable(tries: @max_tries, on: @@exceptions_to_retry_on, sleep: 10) do
retryable(tries: @config.max_tries, on: @@exceptions_to_retry_on, sleep: @config.retry_delay) do
@logger.debug("#{shell} executing:\n#{command}")
output = session.send(shell, command) do |out, err|
block.call(:stdout, out) if block_given? && out
block.call(:stderr, err) if block_given? && err
end
@logger.debug("Output: #{output.inspect}")
# Verify that we didn't get a parser error, and if so we should
# set the exit code to 1. Parse errors return exit code 0 so we
# need to do this.
if output[:exitcode] == 0
(output[:data] || []).each do |data|
next if !data[:stderr]
if data[:stderr].include?("ParserError")
@logger.warn("Detected ParserError, setting exit code to 1")
output[:exitcode] = 1
break
end
end
end
return output
end
end
def raise_winrm_exception(winrm_exception, shell, command)
# If the error is a 401, we can return a more specific error message
if winrm_exception.message.include?("401")
raise Errors::AuthError,
user: @username,
password: @password,
endpoint: endpoint,
message: winrm_exception.message
def raise_winrm_exception(exception, shell = nil, command = nil)
case exception
when WinRM::WinRMAuthorizationError
raise Errors::AuthenticationFailed,
user: @config.username,
password: @config.password,
endpoint: endpoint,
message: exception.message
when WinRM::WinRMHTTPTransportError
raise Errors::ExecutionError,
shell: shell,
command: command,
message: exception.message
when OpenSSL::SSL::SSLError
raise Errors::SSLError, message: exception.message
when HTTPClient::TimeoutError
raise Errors::ConnectionTimeout, message: exception.message
when Errno::ECONNREFUSED
# This is raised if we failed to connect the max amount of times
raise Errors::ConnectionRefused
when Errno::ECONNRESET
# This is raised if we failed to connect the max number of times
# due to an ECONNRESET.
raise Errors::ConnectionReset
when Errno::EHOSTDOWN
# This is raised if we get an ICMP DestinationUnknown error.
raise Errors::HostDown
when Errno::EHOSTUNREACH
# This is raised if we can't work out how to route traffic.
raise Errors::NoRoute
else
raise Errors::ExecutionError,
shell: shell,
command: command,
message: exception.message
end
raise Errors::ExecutionError,
shell: shell,
command: command,
message: winrm_exception.message
end
def new_session
@logger.info("Attempting to connect to WinRM...")
@logger.info(" - Host: #{@host}")
@logger.info(" - Port: #{@port}")
@logger.info(" - Username: #{@username}")
@logger.info(" - Username: #{@config.username}")
@logger.info(" - Transport: #{@config.transport}")
client = ::WinRM::WinRMWebService.new(endpoint, :plaintext, endpoint_options)
client.set_timeout(@timeout_in_seconds)
client = ::WinRM::WinRMWebService.new(endpoint, @config.transport.to_sym, endpoint_options)
client.set_timeout(@config.timeout)
client.toggle_nori_type_casting(:off) #we don't want coersion of types
client
end
@ -131,7 +171,14 @@ module VagrantPlugins
end
def endpoint
"http://#{@host}:#{@port}/wsman"
case @config.transport.to_sym
when :ssl
"https://#{@host}:#{@port}/wsman"
when :plaintext
"http://#{@host}:#{@port}/wsman"
else
raise Errors::WinRMInvalidTransport, transport: @config.transport
end
end
def endpoint_options
@ -139,8 +186,8 @@ module VagrantPlugins
pass: @password,
host: @host,
port: @port,
operation_timeout: @timeout_in_seconds,
basic_auth_only: true }
basic_auth_only: true,
no_ssl_peer_verification: !@config.ssl_peer_verification }
end
end #WinShell class
end

View File

@ -10,9 +10,15 @@ module VagrantPlugins
include Vagrant::Util
def self.configure_networks(machine, networks)
interfaces = Array.new
machine.communicate.sudo("ip -o -0 addr | grep -v LOOPBACK | awk '{print $2}' | sed 's/://'") do |_, result|
interfaces = result.split("\n")
end
networks.each do |network|
entry = TemplateRenderer.render("guests/arch/network_#{network[:type]}",
options: network)
network[:device] = interfaces[network[:interface]]
entry = TemplateRenderer.render("guests/arch/network_#{network[:type]}", options: network)
temp = Tempfile.new("vagrant")
temp.binmode
@ -20,26 +26,8 @@ module VagrantPlugins
temp.close
machine.communicate.upload(temp.path, "/tmp/vagrant_network")
machine.communicate.sudo("ln -sf /dev/null /etc/udev/rules.d/80-net-name-slot.rules")
machine.communicate.sudo("udevadm control --reload")
machine.communicate.sudo("mv /tmp/vagrant_network /etc/netctl/eth#{network[:interface]}")
# Only consider nth line of sed's output below. There's always an
# offset of two lines in the below sed command given the current
# interface number -> 1: lo, 2: nat device,
snth = network[:interface] + 2
# A hack not to rely on udev rule 80-net-name-slot.rules masking
# (ln -sf /dev/null /etc/udev/80-net-name-slot.rules).
# I assume this to be the most portable solution because
# otherwise we would need to rely on the Virtual Machine implementation
# to provide details on the configured interfaces, e.g mac address
# to write a custom udev rule. Templating the netcfg files and
# replacing the correct interface name within ruby seems more
# complicted too (I'm far from being a ruby expert though).
machine.communicate.sudo("sed -i \"s/eth#{network[:interface]}/`ip link | sed -n 's/.*:\\s\\(.*\\): <.*/\\1/p' | sed -n #{snth}p`/g\" /etc/netctl/eth#{network[:interface]}")
machine.communicate.sudo("ip link set eth#{network[:interface]} down")
machine.communicate.sudo("netctl start eth#{network[:interface]}")
machine.communicate.sudo("mv /tmp/vagrant_network /etc/netctl/#{network[:device]}")
machine.communicate.sudo("ip link set #{network[:device]} down && netctl start #{network[:device]}")
end
end
end

View File

@ -0,0 +1,11 @@
module VagrantPlugins
module GuestAtomic
module Cap
class ChangeHostName
def self.change_host_name(machine, name)
machine.communicate.sudo("hostnamectl set-hostname #{name}")
end
end
end
end
end

View File

@ -0,0 +1,11 @@
module VagrantPlugins
module GuestAtomic
module Cap
module Docker
def self.docker_daemon_running(machine)
machine.communicate.test("test -S /run/docker.sock")
end
end
end
end
end

View File

@ -0,0 +1,11 @@
require "vagrant"
module VagrantPlugins
module GuestAtomic
class Guest < Vagrant.plugin("2", :guest)
def detect?(machine)
machine.communicate.test("grep 'ostree=' /proc/cmdline")
end
end
end
end

View File

@ -0,0 +1,25 @@
require 'vagrant'
module VagrantPlugins
module GuestAtomic
class Plugin < Vagrant.plugin("2")
name "Atomic Host guest"
description "Atomic Host guest support."
guest("atomic", "fedora") do
require File.expand_path("../guest", __FILE__)
Guest
end
guest_capability("atomic", "change_host_name") do
require_relative "cap/change_host_name"
Cap::ChangeHostName
end
guest_capability("atomic", "docker_daemon_running") do
require_relative "cap/docker"
Cap::Docker
end
end
end
end

View File

@ -4,7 +4,11 @@ module VagrantPlugins
class ChangeHostName
def self.change_host_name(machine, name)
if !machine.communicate.test("hostname -f | grep '^#{name}$' || hostname -s | grep '^#{name}$'")
machine.communicate.sudo("scutil --set ComputerName #{name}")
machine.communicate.sudo("scutil --set HostName #{name}")
# LocalHostName shouldn't contain dots.
# It is used by Bonjour and visible through file sharing services.
machine.communicate.sudo("scutil --set LocalHostName #{name.gsub(/\.+/, '')}")
machine.communicate.sudo("hostname #{name}")
end
end

View File

@ -0,0 +1,20 @@
module VagrantPlugins
module GuestDarwin
module Cap
module ChooseAddressableIPAddr
def self.choose_addressable_ip_addr(machine, possible)
machine.communicate.tap do |comm|
possible.each do |ip|
command = "ping -c1 -t1 #{ip}"
if comm.test(command)
return ip
end
end
end
nil
end
end
end
end
end

View File

@ -0,0 +1,21 @@
require "vagrant/util/shell_quote"
module VagrantPlugins
module GuestDarwin
module Cap
class InsertPublicKey
def self.insert_public_key(machine, contents)
contents = contents.chomp
contents = Vagrant::Util::ShellQuote.escape(contents, "'")
machine.communicate.tap do |comm|
comm.execute("mkdir -p ~/.ssh")
comm.execute("chmod 0700 ~/.ssh")
comm.execute("printf '#{contents}\\n' >> ~/.ssh/authorized_keys")
comm.execute("chmod 0600 ~/.ssh/authorized_keys")
end
end
end
end
end
end

View File

@ -0,0 +1,37 @@
require "vagrant/util/retryable"
require "shellwords"
module VagrantPlugins
module GuestDarwin
module Cap
class MountSMBSharedFolder
extend Vagrant::Util::Retryable
def self.mount_smb_shared_folder(machine, name, guestpath, options)
expanded_guest_path = machine.guest.capability(:shell_expand_guest_path, guestpath)
mount_point_owner = options[:owner];
if mount_point_owner
machine.communicate.sudo("mkdir -p #{expanded_guest_path}")
machine.communicate.sudo("chown #{mount_point_owner} #{expanded_guest_path}")
else
# fallback to assumption that user has permission
# to create the specified mountpoint
machine.communicate.execute("mkdir -p #{expanded_guest_path}")
end
smb_password = Shellwords.shellescape(options[:smb_password])
mount_options = options[:mount_options];
mount_command = "mount -t smbfs " +
(mount_options ? "-o '#{mount_options.join(",")}' " : "") +
"'//#{options[:smb_username]}:#{smb_password}@#{options[:smb_host]}/#{name}' " +
"#{expanded_guest_path}"
retryable(on: Vagrant::Errors::DarwinMountFailed, tries: 10, sleep: 2) do
machine.communicate.execute(
mount_command,
error_class: Vagrant::Errors::DarwinMountFailed)
end
end
end
end
end
end

View File

@ -0,0 +1,21 @@
require "vagrant/util/shell_quote"
module VagrantPlugins
module GuestDarwin
module Cap
class RemovePublicKey
def self.remove_public_key(machine, contents)
contents = contents.chomp
contents = Vagrant::Util::ShellQuote.escape(contents, "'")
machine.communicate.tap do |comm|
if comm.test("test -f ~/.ssh/authorized_keys")
comm.execute(
"sed -i '' '/^.*#{contents}.*$/d' ~/.ssh/authorized_keys")
end
end
end
end
end
end
end

View File

@ -17,7 +17,7 @@ module VagrantPlugins
end
def self.rsync_post(machine, opts)
if opts.has_key?(:chown) && !opts[:chown]
if opts.key?(:chown) && !opts[:chown]
return
end

View File

@ -16,6 +16,11 @@ module VagrantPlugins
Cap::ChangeHostName
end
guest_capability("darwin", "choose_addressable_ip_addr") do
require_relative "cap/choose_addressable_ip_addr"
Cap::ChooseAddressableIPAddr
end
guest_capability("darwin", "configure_networks") do
require_relative "cap/configure_networks"
Cap::ConfigureNetworks
@ -26,16 +31,31 @@ module VagrantPlugins
Cap::Halt
end
guest_capability("darwin", "insert_public_key") do
require_relative "cap/insert_public_key"
Cap::InsertPublicKey
end
guest_capability("darwin", "mount_nfs_folder") do
require_relative "cap/mount_nfs_folder"
Cap::MountNFSFolder
end
guest_capability("darwin", "mount_smb_shared_folder") do
require_relative "cap/mount_smb_shared_folder"
Cap::MountSMBSharedFolder
end
guest_capability("darwin", "mount_vmware_shared_folder") do
require_relative "cap/mount_vmware_shared_folder"
Cap::MountVmwareSharedFolder
end
guest_capability("darwin", "remove_public_key") do
require_relative "cap/remove_public_key"
Cap::RemovePublicKey
end
guest_capability("darwin", "rsync_installed") do
require_relative "cap/rsync"
Cap::RSync

View File

@ -13,9 +13,8 @@ module VagrantPlugins
machine.communicate.tap do |comm|
# First, remove any previous network modifications
# from the interface file.
comm.sudo("sed -e '/^#VAGRANT-BEGIN/,/^#VAGRANT-END/ d' /etc/network/interfaces > /tmp/vagrant-network-interfaces")
comm.sudo("su -c 'cat /tmp/vagrant-network-interfaces > /etc/network/interfaces'")
comm.sudo("rm -f /tmp/vagrant-network-interfaces")
comm.sudo("sed -e '/^#VAGRANT-BEGIN/,$ d' /etc/network/interfaces > /tmp/vagrant-network-interfaces.pre")
comm.sudo("sed -ne '/^#VAGRANT-END/,$ p' /etc/network/interfaces | tac | sed -e '/^#VAGRANT-END/,$ d' | tac > /tmp/vagrant-network-interfaces.post")
# Accumulate the configurations to add to the interfaces file as
# well as what interfaces we're actually configuring since we use that
@ -47,8 +46,8 @@ module VagrantPlugins
comm.sudo("/sbin/ip addr flush dev eth#{interface} 2> /dev/null")
end
comm.sudo("cat /tmp/vagrant-network-entry >> /etc/network/interfaces")
comm.sudo("rm -f /tmp/vagrant-network-entry")
comm.sudo('cat /tmp/vagrant-network-interfaces.pre /tmp/vagrant-network-entry /tmp/vagrant-network-interfaces.post > /etc/network/interfaces')
comm.sudo('rm -f /tmp/vagrant-network-interfaces.pre /tmp/vagrant-network-entry /tmp/vagrant-network-interfaces.post')
# Bring back up each network interface, reconfigured
interfaces.each do |interface|

View File

@ -2,7 +2,7 @@ module VagrantPlugins
module GuestDebian
class Guest < Vagrant.plugin("2", :guest)
def detect?(machine)
machine.communicate.test("cat /etc/issue | grep 'Debian'")
machine.communicate.test("cat /etc/issue | grep 'Debian' | grep -v '8'")
end
end
end

View File

@ -0,0 +1,16 @@
module VagrantPlugins
module GuestDebian8
module Cap
class Halt
def self.halt(machine)
begin
machine.communicate.sudo("shutdown -h -H")
rescue IOError
# Do nothing, because it probably means the machine shut down
# and SSH connection was lost.
end
end
end
end
end
end

View File

@ -0,0 +1,9 @@
module VagrantPlugins
module GuestDebian8
class Guest < Vagrant.plugin("2", :guest)
def detect?(machine)
machine.communicate.test("cat /etc/issue | grep 'Debian' | grep '8'")
end
end
end
end

View File

@ -0,0 +1,21 @@
require "vagrant"
module VagrantPlugins
module GuestDebian8
class Plugin < Vagrant.plugin("2")
name "Debian Jessie guest"
description "Debian Jessie guest support."
guest("debian8", "debian") do
require File.expand_path("../guest", __FILE__)
Guest
end
guest_capability("debian8", "halt") do
require_relative "cap/halt"
Cap::Halt
end
end
end
end

Some files were not shown because too many files have changed in this diff Show More