This commit is contained in:
Ben Hines 2015-04-11 13:03:18 -07:00
commit e8843b8648
384 changed files with 12446 additions and 1839 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

@ -2,6 +2,9 @@ language: ruby
before_install:
- sudo apt-get update -qq
- sudo apt-get install -qq -y bsdtar
- rvm @global do gem uninstall bundler --all --executables
- gem uninstall bundler --all --executables
- gem install bundler --version '< 1.7.0'
rvm:
- 2.0.0
env:

View File

@ -1,3 +1,230 @@
## 1.7.3 (unreleased)
IMPROVEMENTS:
- guests/darwin: Support inserting generated key. [GH-5204]
- guests/fedora: Support Fedora 21. [GH-5277]
- guests/redhat: Support Scientific Linux 7 [GH-5303]
- guests/solaris,solaris11: Support inserting generated key. [GH-5182]
[GH-5290]
- providers/virtualbox: regexp supported for bridge configuration. [GH-5320]
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/cli: fix box checksum validation [GH-4665, GH-5221]
- communicators/winrm: improve error handling significantly and improve
the error messages shown to be more human-friendly. [GH-4943]
- hosts/nfs: allow colons (`:`) in NFS IDs [GH-5222]
- guests/debian: Halt works properly on Debian 8. [GH-5369]
- guests/funtoo: fix incorrect path in configure networks [GH-4812]
- 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]
- 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/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]
- 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/docker: Only add docker user to group if exists. [GH-5315]
- 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/docker: use docker.com instead of docker.io [GH-5216]
- pushes/atlas: send additional box metadata [GH-5283]
- pushes/ftp: improve check for remote directory existence [GH-5549]
- synced\_folders/rsync: add `IdentitiesOnly=yes` to the rsync command. [GH-5175]
- 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 file 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 +235,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.

10
Vagrantfile vendored
View File

@ -6,13 +6,21 @@
Vagrant.configure("2") do |config|
config.vm.box = "hashicorp/precise64"
["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

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

View File

@ -65,7 +65,7 @@ _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
;;
@ -111,7 +111,7 @@ _vagrant() {
then
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
;;

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
@ -141,13 +142,22 @@ module Vagrant
# 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 }
def self.has_plugin?(name, version=nil)
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.

View File

@ -147,7 +147,7 @@ module Vagrant
# element is an authenticated URL.
# @param [Hash] env
# @param [Bool] expanded True if the metadata URL was expanded with
# a Vagrant Cloud server URL.
# a Atlas server URL.
def add_from_metadata(url, env, expanded)
original_url = env[:box_url]
provider = env[:box_provider]
@ -369,7 +369,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,7 +401,7 @@ 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]
@ -409,7 +409,7 @@ module Vagrant
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)
@ -483,7 +483,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,8 @@ 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_formats = machine.provider_options[:box_format] ||
machine.provider_name
@ -79,12 +81,15 @@ 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,
}))
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

@ -93,11 +93,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 +113,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 +129,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

@ -1,4 +1,5 @@
require 'fileutils'
require "pathname"
require 'vagrant/util/safe_chdir'
require 'vagrant/util/subprocess'
@ -36,6 +37,9 @@ module Vagrant
@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 +85,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 +99,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

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

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

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.
@ -497,6 +539,41 @@ module Vagrant
end
end
# This executes the push with the given name, raising any exceptions that
# occur.
#
# Precondition: the push is not nil and exists.
def push(name)
@logger.info("Getting push: #{name}")
name = name.to_sym
pushes = self.vagrantfile.config.push.__compiled_pushes
if !pushes.key?(name)
raise Vagrant::Errors::PushStrategyNotDefined,
name: name,
pushes: pushes.keys
end
strategy, config = pushes[name]
push_registry = Vagrant.plugin("2").manager.pushes
klass, _ = push_registry.get(strategy)
if klass.nil?
raise Vagrant::Errors::PushStrategyNotLoaded,
name: strategy,
pushes: push_registry.keys
end
klass.new(self, config).push
end
# The list of pushes defined in this Vagrantfile.
#
# @return [Array<Symbol>]
def pushes
self.vagrantfile.config.push.__compiled_pushes.keys
end
# This returns a machine with the proper provider for this environment.
# The machine named by `name` must be in this environment.
#
@ -519,7 +596,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
@ -782,7 +859,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

@ -476,6 +476,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 +556,22 @@ module Vagrant
error_key(:plugin_uninstall_system)
end
class PushesNotDefined < VagrantError
error_key(:pushes_not_defined)
end
class PushStrategyNotDefined < VagrantError
error_key(:push_strategy_not_defined)
end
class PushStrategyNotLoaded < VagrantError
error_key(:push_strategy_not_loaded)
end
class PushStrategyNotProvided < VagrantError
error_key(:push_strategy_not_provided)
end
class RSyncError < VagrantError
error_key(:rsync_error)
end
@ -616,6 +636,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
@ -728,6 +752,14 @@ module Vagrant
error_key(:virtualbox_no_name)
end
class VirtualBoxNameExists < VagrantError
error_key(:virtualbox_name_exists)
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,9 +150,18 @@ 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
@ -155,7 +172,7 @@ module Vagrant
# 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
@ -342,6 +360,7 @@ module Vagrant
# This reloads the ID of the underlying machine.
def reload
old_id = @id
@id = nil
if @data_dir
@ -350,6 +369,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
@ -434,6 +460,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 +480,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

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

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

@ -136,9 +136,9 @@ module Vagrant
# 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)
@ -249,7 +249,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 +284,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 +294,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.

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

@ -58,18 +58,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 +118,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 +197,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

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

@ -0,0 +1,43 @@
module Vagrant
module Util
class Env
#
# 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(&block)
original = ENV.to_hash
ENV.delete('_ORIGINAL_GEM_PATH')
ENV.delete_if { |k,_| k.start_with?('BUNDLE_') }
ENV.delete_if { |k,_| k.start_with?('GEM_') }
ENV.delete_if { |k,_| k.start_with?('RUBY') }
yield
ensure
ENV.replace(original.to_hash)
end
end
end
end

View File

@ -29,7 +29,7 @@ module Vagrant
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,46 @@
require "base64"
require "openssl"
module Vagrant
module Util
class Keypair
# 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)
rsa_key = OpenSSL::PKey::RSA.new(2048)
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

@ -148,7 +148,7 @@ module Vagrant
# 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

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

View File

@ -25,6 +25,7 @@ module Vagrant
def initialize(*command)
@options = command.last.is_a?(Hash) ? command.pop : {}
@command = command.dup
@command.each { |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?

View File

@ -114,24 +114,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 +159,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

@ -47,7 +47,7 @@ module VagrantPlugins
end
o.separator ""
o.separator "The box descriptor can be the name of a box on Vagrant Cloud,"
o.separator "The box descriptor can be the name of a box on HashiCorp's Atlas,"
o.separator "or a URL, or a local .box file, or a local .json file containing"
o.separator "the catalog metadata."
o.separator ""

View File

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

@ -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,7 +320,7 @@ 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)
# Build the options we'll use to initiate the connection via Net::SSH
common_connect_opts = {
@ -305,11 +337,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
@ -594,6 +621,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
@ -79,12 +146,21 @@ module VagrantPlugins
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,11 +182,8 @@ 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
@ -156,8 +229,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 +240,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

@ -27,7 +27,7 @@ $task_xml = @'
<StartWhenAvailable>false</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>true</StopOnIdleEnd>
<StopOnIdleEnd>false</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>

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

@ -4,7 +4,9 @@ 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}")
machine.communicate.sudo("scutil --set LocalHostName #{name}")
machine.communicate.sudo("hostname #{name}")
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,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

@ -26,6 +26,11 @@ 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
@ -36,6 +41,11 @@ module VagrantPlugins
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 | tail -n +2 > /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

View File

@ -26,7 +26,7 @@ module VagrantPlugins
end
interface_names = networks.map do |network|
"eth#{network[:interface]}"
"#{interface_names[network[:interface]]}"
end
else
machine.communicate.sudo("/usr/sbin/biosdevname -d | grep Kernel | cut -f2 -d: | sed -e 's/ //;'") do |_, result|

View File

@ -4,7 +4,7 @@ module VagrantPlugins
class NetworkScriptsDir
# The path to the directory with the network configuration scripts.
# This is pulled out into its own directory since there are other
# operating systems (SuSE) which behave similarly but with a different
# operating systems (SUSE) which behave similarly but with a different
# path to the network scripts.
def self.network_scripts_dir(machine)
"/etc/sysconfig/network-scripts"

View File

@ -4,7 +4,7 @@ module VagrantPlugins
module GuestFedora
class Guest < Vagrant.plugin("2", :guest)
def detect?(machine)
machine.communicate.test("grep 'Fedora release [12][67890]' /etc/redhat-release")
machine.communicate.test("grep 'Fedora release [12][678901]' /etc/redhat-release")
end
end
end

View File

@ -4,8 +4,15 @@ module VagrantPlugins
class MountNFSFolder
def self.mount_nfs_folder(machine, ip, folders)
folders.each do |name, opts|
if opts[:nfs_version]
nfs_version_mount_option="-o nfsv#{opts[:nfs_version]}"
end
machine.communicate.sudo("mkdir -p #{opts[:guestpath]}", {shell: "sh"})
machine.communicate.sudo("mount -t nfs '#{ip}:#{opts[:hostpath]}' '#{opts[:guestpath]}'", {shell: "sh"})
machine.communicate.sudo(
"mount -t nfs #{nfs_version_mount_option} " +
"'#{ip}:#{opts[:hostpath]}' '#{opts[:guestpath]}'", {shell: "sh"})
end
end
end

View File

@ -0,0 +1,21 @@
require "vagrant/util/shell_quote"
module VagrantPlugins
module GuestFreeBSD
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

@ -31,7 +31,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

@ -36,6 +36,11 @@ module VagrantPlugins
Cap::MountNFSFolder
end
guest_capability("freebsd", "remove_public_key") do
require_relative "cap/remove_public_key"
Cap::RemovePublicKey
end
guest_capability("freebsd", "rsync_install") do
require_relative "cap/rsync"
Cap::RSync

View File

@ -28,7 +28,7 @@ module VagrantPlugins
temp.binmode
temp.write(entry)
temp.close
comm.upload(temp.path, "/tmp/vagrant-network-entry-#{ifFile}")
comm.upload(temp.path, "/tmp/vagrant-#{ifFile}")
comm.sudo("cp /tmp/vagrant-#{ifFile} /etc/conf.d/#{ifFile}")
comm.sudo("chmod 0644 /etc/conf.d/#{ifFile}")
comm.sudo("ln -fs /etc/init.d/netif.tmpl /etc/init.d/#{ifFile}")

View File

@ -51,10 +51,15 @@ module VagrantPlugins
while true
success = true
stderr = ""
mount_commands.each do |command|
no_such_device = false
stderr = ""
status = machine.communicate.sudo(command, error_check: false) do |type, data|
no_such_device = true if type == :stderr && data =~ /No such device/i
if type == :stderr
no_such_device = true if data =~ /No such device/i
stderr += data.to_s
end
end
success = status == 0 && !no_such_device
@ -69,7 +74,8 @@ module VagrantPlugins
command.gsub!(smb_password, "PASSWORDHIDDEN")
raise Vagrant::Errors::LinuxMountFailed,
command: command
command: command,
output: stderr
end
sleep 2

View File

@ -0,0 +1,21 @@
require "vagrant/util/shell_quote"
module VagrantPlugins
module GuestLinux
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

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