diff --git a/.gitignore b/.gitignore index 225e014d6..b1274f4d0 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/.travis.yml b/.travis.yml index 9baf9785e..bd19bb951 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,23 @@ language: ruby -before_install: - - sudo apt-get update -qq - - sudo apt-get install -qq -y bsdtar + +sudo: false + +cache: bundler + +addons: + apt: + packages: + - bsdtar + rvm: - 2.0.0 + +branches: + only: + - master + env: global: - NOKOGIRI_USE_SYSTEM_LIBRARIES=true -script: rake test:unit + +script: bundle exec rake test:unit diff --git a/CHANGELOG.md b/CHANGELOG.md index b9be106c2..6b34088fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,334 @@ +## 1.7.4 (unreleased) + + + +## 1.7.3 (July 10, 2015) + +FEATURES: + + - **New guest: `atomic`* - Project Atomic is supported as a guest + - providers/virtualbox: add support for 5.0 [GH-5647] + +IMPROVEMENTS: + + - core: add password authentication to rdp_info hash [GH-4726] + - core: improve error message when packaging fails [GH-5399] + - core: improve message when adding a box from a file path [GH-5395] + - core: add support for network gateways [GH-5721] + - core: allow redirecting stdout and stderr in the UI [GH-5433] + - core: update version of winrm-fs to 0.2.0 [GH-5738] + - core: add option to enabled trusted http(s) redirects [GH-4422] + - core: capture additional information such as line numbers during + Vagrantfile loading [GH-4711, GH-5769] + - core: add .color? to UI objects to see if they support color [GH-5771] + - core: ignore hidden directories when searching for boxes [GH-5748, GH-5785] + - core: use `config.ssh.sudo_command` to customize the sudo command + format [GH-5573] + - core: add `Vagrant.original_env` for Vagrant and plugins to restore or + inspect the original environment when Vagrant is being run from the + installer [GH-5910] + - guests/darwin: support inserting generated key [GH-5204] + - guests/darwin: support mounting SMB shares [GH-5750] + - guests/fedora: support Fedora 21 [GH-5277] + - guests/fedora: add capabilities for nfs and flavor [GH-5770, GH-4847] + - guests/linux: specify user's domain as separate parameter [GH-3620, GH-5512] + - guests/redhat: support Scientific Linux 7 [GH-5303] + - guests/photon: initial support [GH-5612] + - guests/solaris,solaris11: support inserting generated key [GH-5182] + [GH-5290] + - providers/docker: images are pulled prior to starting [GH-5249] + - provisioners/ansible: store the first ssh private key in the auto-generated inventory [GH-5765] + - provisioners/chef: add capability for checking if Chef is installed on Windows [GH-5669] + - provisioners/docker: restart containers if arguments have changed [GH-3055, GH-5924] + - provisioners/puppet: add support for Puppet 4 and configuration options [GH-5601] + - provisioners/puppet: add support for `synced_folder_args` in apply [GH-5359] + - provisioners/salt: add configurable `config_dir` [GH-3138] + - provisioners/salt: add support for masterless configuration [GH-3235] + - provisioners/salt: provider path to missing file in errors [GH-5637] + - provisioners/salt: add ability to run salt orchestrations [GH-4371] + - provisioners/salt: update to 2015.5.2 [GH-4152, GH-5437] + - provisioners/salt: support specifying version to install [GH-5892] + - provisioners/shell: add :name attribute to shell provisioner [GH-5607] + - providers/docker: supports file downloads with the file provisioner [GH-5651] + - providers/docker: support named Dockerfile [GH-5480] + - providers/docker: don't remove image on reload so that build cache can + be used fully [GH-5905] + - providers/hyperv: select a Hyper-V switch based on a `network_name` [GH-5207] + - providers/hyperv: allow configuring VladID [GH-5539] + - providers/virtualbox: regexp supported for bridge configuration [GH-5320] + - providers/virtualbox: handle a list of bridged NICs [GH-5691] + - synced_folders/rsync: allow showing rsync output in debug mode [GH-4867] + - synced_folders/rsync: set `rsync__rsync_path` to specify the remote + command used to execute rsync [GH-3966] + +BUG FIXES: + + - core: push configurations are validated with global configs [GH-5130] + - core: remove executable permissions on internal file [GH-5220] + - core: check name and version in `has_plugin?` [GH-5218] + - core: do not create duplicates when defining two private network addresses [GH-5325] + - core: update ssh to check for Plink [GH-5604] + - core: do not report plugins as installed when plugins are disabled [GH-5698, GH-5430] + - core: Only take files when packaging a box to avoid duplicates [GH-5658, GH-5657] + - core: escape curl urls and authentication [GH-5677] + - core: fix crash if a value is missing for CLI arguments [GH-5550] + - core: retry SSH key generation for transient RSA errors [GH-5056] + - core: `ssh.private_key_path` will override the insecure key [GH-5632] + - core: restore the original environment when shelling out to subprocesses + outside of the installer [GH-5912] + - core/cli: fix box checksum validation [GH-4665, GH-5221] + - core/windows: allow Windows UNC paths to allow more than 256 + characters [GH-4815] + - command/rsync-auto: don't crash if rsync command fails [GH-4991] + - communicators/winrm: improve error handling significantly and improve + the error messages shown to be more human-friendly. [GH-4943] + - communicators/winrm: remove plaintext passwords from files after + provisioner is complete [GH-5818] + - hosts/nfs: allow colons (`:`) in NFS IDs [GH-5222] + - guests/darwin: remove dots from LocalHostName [GH-5558] + - guests/debian: Halt works properly on Debian 8. [GH-5369] + - guests/fedora: recognize future fedora releases [GH-5730] + - guests/fedora: reload iface connection by NetworkManager [GH-5709] + - guests/fedora: do not use biosdevname if it is not installed [GH-5707] + - guests/freebsd: provide an argument to the backup file [GH-5516, GH-5517] + - guests/funtoo: fix incorrect path in configure networks [GH-4812] + - guests/linux: fix edge case exception where no home directory + is available on guest [GH-5846] + - guests/linux: copy NFS exports to tmpdir to do edits to guarantee + permissions are available [GH-5773] + - guests/openbsd: output newline after inserted public key [GH-5881] + - guests/tinycore: fix change hostname functionality [GH-5623] + - guests/ubuntu: use `hostnamectl` to set hostname on Ubuntu Vivid [GH-5753] + - guests/windows: Create rsync folder prior to rsync-ing. [GH-5282] + - guests/windows: Changing hostname requires reboot again since + the non-reboot code path was crashing Windows server. [GH-5261] + - guests/windows: ignore virtual NICs [GH-5478] + - hosts/windows: More accurately get host IP address in VPNs. [GH-5349] + - plugins/login: allow users to login with a token [GH-5145] + - providers/docker: Build image from `/var/lib/docker` for more disk + space on some systems. [GH-5302] + - providers/docker: Fix crash that could occur in some scenarios when + the host VM path changed. + - providers/docker: Fix crash that could occur on container destroy + with VirtualBox shared folders [GH-5143] + - providers/hyperv: allow users to configure memory, cpu count, and vmname [GH-5183] + - providers/hyperv: import respects secure boot. [GH-5209] + - providers/hyperv: only set EFI secure boot for gen 2 machines [GH-5538] + - providers/virtualbox: read netmask from dhcpservers [GH-5233] + - providers/virtualbox: Fix exception when VirtualBox version is empty. [GH-5308] + - providers/virtualbox: Fix exception when VBoxManage.exe can't be run + on Windows [GH-1483] + - providers/virtualbox: Error if another user is running after a VM is + created to avoid issue with VirtualBox "losing" the VM [GH-5895] + - providers/virtualbox: The "name" setting on private networks will + choose an existing hostonly network [GH-5389] + - provisioners/ansible: fix SSH settings to support more than 5 ssh keys [GH-5017] + - provisioners/ansible: increase ansible connection timeout to 30 seconds [GH-5018] + - provisioners/ansible: disable color if Vagrant is not colored [GH-5531, GH-5532] + - provisioners/ansible: only show ansible-playbook command when `verbose` option is enabled [GH-5803] + - provisioners/ansible: fix a race condition in the inventory file generation [GH-5551] + - provisioners/docker: use `service` to restart Docker instad of upstart [GH-5245, GH-5577] + - provisioners/docker: Only add docker user to group if exists. [GH-5315] + - provisioners/docker: Use https for repo [GH-5749] + - provisioners/docker: `apt-get update` before installing linux kernel + images to get the correct version [GH-5860] + - provisioners/chef: Fix shared folders missing error [GH-5199] + - provisioners/chef: Use `command -v` to check for binary instead of + `which` since that doesn't exist on some systems. [GH-5170] + - provisioners/chef-zero: support more chef-zero/local mode attributes [GH-5339] + - provisioners/chef: use windows-specific paths in Chef provisioners [GH-5913] + - provisioners/docker: use docker.com instead of docker.io [GH-5216] + - provisioners/docker: use `--restart` instead of `-r` on daemon [GH-4477] + - provisioners/file: validation of source is relative to Vagrantfile [GH-5252] + - pushes/atlas: send additional box metadata [GH-5283] + - pushes/local-exec: fix "text file busy" error for inline [GH-5695] + - pushes/ftp: improve check for remote directory existence [GH-5549] + - synced\_folders/rsync: add `IdentitiesOnly=yes` to the rsync command. [GH-5175] + - synced\_folders/smb: use correct `password` option [GH-5805] + - synced\_folders/smb: prever IPv4 over IPv6 address to mount [GH-5798] + - virtualbox/config: fix misleading error message for private_network [GH-5536, GH-5418] + +## 1.7.2 (January 6, 2015) + +BREAKING CHANGES: + + - If you depended on the paths that Chef/Puppet provisioners use to + store cookbooks (ex. "/tmp/vagrant-chef-1"), these will no longer be + correct. Without this change, Chef/Puppet didn't work at all with + `vagrant provision`. We expect this to affect only a minor number of + people, since it's not something that was ever documented or recommended + by Vagrant, or even meant to be supported. + +FEATURES: + + - provisioners/salt: add support for grains [GH-4895] + +IMPROVEMENTS: + + - commands/reload,up: `--provision-with` implies `--provision` [GH-5085] + +BUG FIXES: + + - core: private boxes still referencing vagrantcloud.com will have + their vagrant login access token properly appended + - core: push plugin configuration is properly validated + - core: restore box packaging functionality + - commands/package: fix crash + - commands/push: push lookups are by user-defined name, not push + strategy name [GH-4975] + - commands/push: validate the configuration + - communicators/winrm: detect parse errors in PowerShell and error + - guests/arch: fix network configuration due to poor line breaks. [GH-4964] + - guests/solaris: Merge configurations properly so configs can be set + in default Vagrantfiles. [GH-5092] + - installer: SSL cert bundle contains 1024-bit keys, fixing SSL verification + for a lot of sites. + - installer: vagrant executable properly `cygpaths` the SSL bundle path + for Cygwin + - installer: Nokogiri (XML lib used by Vagrant and dependencies) linker + dependencies fixed, fixing load issues on some platforms + - providers/docker: Symlinks in shared folders work. [GH-5093] + - providers/hyperv: VM start errors turn into proper Vagrant errors. [GH-5101] + - provisioners/chef: fix missing shared folder error [GH-4988] + - provisioners/chef: remove Chef version check from solo.rb generation and + make `roles_path` populate correctly + - provisioners/chef: fix bad invocation of `with_clean_env` [GH-5021] + - pushes/atlas: support more verbose logging + - pushes/ftp: expand file paths relative to the Vagrantfile + - pushes/ftp: improved debugging output + - pushes/ftp: create parent directories if they do not exist on the remote + server + +## 1.7.1 (December 11, 2014) + +IMPROVEMENTS: + + - provisioners/ansible: Use Docker proxy if needed. [GH-4906] + +BUG FIXES: + + - providers/docker: Add support of SSH agent forwarding. [GH-4905] + +## 1.7.0 (December 9, 2014) + +BREAKING CHANGES: + + - provisioners/ansible: `raw_arguments` has now highest priority + - provisioners/ansible: only the `ssh` connection transport is supported + (`paramiko` can be enabled with `raw_arguments` at your own risks) + +FEATURES: + + - **Vagrant Push**: Vagrant can now deploy! `vagrant push` is a single + command to deploy your application. Deploy to Heroku, FTP, or + HashiCorp's commercial product Atlas. New push strategies can be + added with plugins. + - **Named provisioners**: Provisioners can now be named. This name is used + for output as well as `--provision-with` for better control. + - Default provider logic improved: Providers in `config.vm.provider` blocks + in your Vagrantfile now have higher priority than plugins. Earlier + providers are chosen before later ones. [GH-3812] + - If the default insecure keypair is used, Vagrant will automatically replace + it with a randomly generated keypair on first `vagrant up`. [GH-2608] + - Vagrant Login is now part of Vagrant core + - Chef Zero provisioner: Use Chef 11's "local" mode to run recipes against an + in-memory Chef Server + - Chef Apply provisioner: Specify inline Chef recipes and recipe snippets + using the Chef Apply provisioner + +IMPROVEMENTS: + + - core: `has_plugin?` function now takes a second argument which is a + version constraint requirement. [GH-4650] + - core: ".vagrantplugins" file in the same folder as your Vagrantfile + will be loaded for defining inline plugins. [GH-3775] + - commands/plugin: Plugin list machine-readable output contains the plugin + name as the target for versions and other info. [GH-4506] + - env/with_cleanenv: New helper for plugin developers to use when shelling out + to another Ruby environment + - guests/arch: Support predictable network interface naming. [GH-4468] + - guests/suse: Support NFS client install, rsync setup. [GH-4492] + - guests/tinycore: Support changing host names. [GH-4469] + - guests/tinycore: Support DHCP-based networks. [GH-4710] + - guests/windows: Hostname can be set without reboot. [GH-4687] + - providers/docker: Build output is now shown. [GH-3739] + - providers/docker: Can now start containers from private repositories + more easily. Vagrant will login for you if you specify auth. [GH-4042] + - providers/docker: `stop_timeout` can be used to modify the `docker stop` + timeout. [GH-4504] + - provisioners/chef: Automatically install Chef when using a Chef provisioner. + - provisioners/ansible: Always show Ansible command executed when Vagrant log + level is debug (even if ansible.verbose is false) + - synced\_folders/nfs: Won't use `sudo` to write to /etc/exports if there + are write privileges. [GH-2643] + - synced\_folders/smb: Credentials from one SMB will be copied to the rest. [GH-4675] + +BUG FIXES: + + - core: Fix cases where sometimes SSH connection would hang. + - core: On a graceful halt, force halt if capability "insert public key" + is missing. [GH-4684] + - core: Don't share `/vagrant` if any "." folder is shared. [GH-4675] + - core: Fix SSH private key permissions more aggressively. [GH-4670] + - core: Custom Vagrant Cloud server URL now respected in more cases. + - core: On downloads, don't continue downloads if the remote server + doesn't support byte ranges. [GH-4479] + - core: Box downloads recognize more complex content types that include + "application/json" [GH-4525] + - core: If all sub-machines are `autostart: false`, don't start any. [GH-4552] + - core: Update global-status state in more cases. [GH-4513] + - core: Only delete machine state if the machine is not created in initialize + - commands/box: `--cert` flag works properly. [GH-4691] + - command/docker-logs: Won't crash if container is removed. [GH-3990] + - command/docker-run: Synced folders will be attached properly. [GH-3873] + - command/rsync: Sync to Docker containers properly. [GH-4066] + - guests/darwin: Hostname sets bonjour name and local host name. [GH-4535] + - guests/freebsd: NFS mounting can specify the version. [GH-4518] + - guests/linux: More descriptive error message if SMB mount fails. [GH-4641] + - guests/rhel: Hostname setting on 7.x series works properly. [GH-4527] + - guests/rhel: Installing NFS client works properly on 7.x [GH-4499] + - guests/solaris11: Static IP address preserved after restart. [GH-4621] + - guests/ubuntu: Detect with `lsb_release` instead of `/etc/issue`. [GH-4565] + - hosts/windows: RDP client shouldn't map all drives by default. [GH-4534] + - providers/docker: Create args works. [GH-4526] + - providers/docker: Nicer error if package is called. [GH-4595] + - providers/docker: Host IP restriction is forwarded through. [GH-4505] + - providers/docker: Protocol is now honored in direct `ports settings. + - providers/docker: Images built using `build_dir` will more robustly + capture the final image. [GH-4598] + - providers/docker: NFS synced folders now work. [GH-4344] + - providers/docker: Read the created container ID more robustly. + - providers/docker: `vagrant share` uses correct IP of proxy VM if it + exists. [GH-4342] + - providers/docker: `vagrant_vagrantfile` expands home directory. [GH-4000] + - providers/docker: Fix issue where multiple identical proxy VMs would + be created. [GH-3963] + - providers/docker: Multiple links with the same name work. [GH-4571] + - providers/virtualbox: Show a human-friendly error if VirtualBox didn't + clean up an existing VM. [GH-4681] + - providers/virtualbox: Detect case when VirtualBox reports 0.0.0.0 as + IP address and don't allow it. [GH-4671] + - providers/virtualbox: Show more descriptive error if VirtualBox is + reporting an empty version. [GH-4657] + - provisioners/ansible: Force `ssh` (OpenSSH) connection by default [GH-3396] + - provisioners/ansible: Don't use or modify `~/.ssh/known_hosts` file by default, + similarly to native vagrant commands [GH-3900] + - provisioners/ansible: Use intermediate Docker host when needed. [GH-4071] + - provisioners/docker: Get GPG key over SSL. [GH-4597] + - provisioners/docker: Search for docker binary in multiple places. [GH-4580] + - provisioners/salt: Highstate works properly with a master. [GH-4471] + - provisioners/shell: Retry getting SSH info a few times. [GH-3924] + - provisioners/shell: PowerShell scripts can have args. [GH-4548] + - synced\_folders/nfs: Don't modify NFS exports file if no exports. [GH-4619] + - synced\_folders/nfs: Prune exports for file path IDs. [GH-3815] + +PLUGIN AUTHOR CHANGES: + + - `Machine#action` can be called with the option `lock: false` to not + acquire a machine lock. + - `Machine#reload` will now properly trigger the `machine_id_changed` + callback on providers. + ## 1.6.5 (September 4, 2014) BUG FIXES: @@ -8,6 +339,8 @@ BUG FIXES: - guests/redhat: Fix typo causing crash in configuring networks. [GH-4438] - guests/redhat: Fix typo causing hostnames to not set. [GH-4443] - providers/virtualbox: NFS works when using DHCP private network. [GH-4433] + - provisioners/salt: Fix error when removing non-existent bootstrap script + on Windows. [GH-4614] ## 1.6.4 (September 2, 2014) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fc5d3c576..bdad0b6df 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 diff --git a/Gemfile b/Gemfile index 3ddf8ced5..15f245819 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,4 @@ -source "http://rubygems.org" +source "https://rubygems.org" gemspec diff --git a/LICENSE b/LICENSE index 661398b6b..91ca85a81 100644 --- a/LICENSE +++ b/LICENSE @@ -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 diff --git a/README.md b/README.md index 7e1325dbf..a361da9b0 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/Vagrantfile b/Vagrantfile index 075157ed9..1e20fca33 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -5,17 +5,28 @@ Vagrant.configure("2") do |config| config.vm.box = "hashicorp/precise64" + config.vm.hostname = "vagrant" + config.ssh.shell = "bash -c 'BASH_ENV=/etc/profile exec bash'" - ["virtualbox", "vmware_fusion", "vmware_workstation"].each do |provider| + ["vmware_fusion", "vmware_workstation", "virtualbox"].each do |provider| config.vm.provider provider do |v, override| v.memory = "1024" end end config.vm.provision "shell", inline: $shell + + config.push.define "www", strategy: "local-exec" do |push| + push.script = "scripts/website_push_www.sh" + end + + config.push.define "docs", strategy: "local-exec" do |push| + push.script = "scripts/website_push_docs.sh" + end end $shell = <<-CONTENTS +export DEBIAN_FRONTEND=noninteractive MARKER_FILE="/usr/local/etc/vagrant_provision_marker" # Only provision once @@ -29,8 +40,11 @@ apt-get update # Install basic dependencies apt-get install -y build-essential bsdtar curl +# Import the mpapis public key to verify downloaded releases +su -l -c 'curl -sSL https://rvm.io/mpapis.asc | gpg -q --import -' vagrant + # Install RVM -su -l -c 'curl -L https://get.rvm.io | bash -s stable' vagrant +su -l -c 'curl -sL https://get.rvm.io | bash -s stable' vagrant # Add the vagrant user to the RVM group #usermod -a -G rvm vagrant diff --git a/bin/vagrant b/bin/vagrant index 21630e1fc..fce68c857 100755 --- a/bin/vagrant +++ b/bin/vagrant @@ -15,7 +15,7 @@ end # Fast path the version of Vagrant if argv.include?("-v") || argv.include?("--version") - require "vagrant/version" + require_relative "../lib/vagrant/version" puts "Vagrant #{Vagrant::VERSION}" exit 0 end @@ -68,7 +68,7 @@ end # then also initialize the paths to the plugins. require "bundler" begin - Bundler.setup(:default, :plugins) + $vagrant_bundler_runtime = Bundler.setup(:default, :plugins) rescue Bundler::GemNotFound $stderr.puts "Bundler, the underlying system used to manage Vagrant plugins," $stderr.puts "is reporting that a plugin or its dependency can't be found." diff --git a/contrib/bash/completion.sh b/contrib/bash/completion.sh index 636795744..2b201d1ba 100644 --- a/contrib/bash/completion.sh +++ b/contrib/bash/completion.sh @@ -65,13 +65,18 @@ _vagrant() { then case "$prev" in "init") - local box_list=$(find $HOME/.vagrant.d/boxes -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) + local box_list=$(find "${VAGRANT_HOME:-${HOME}/.vagrant.d}/boxes" -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) COMPREPLY=($(compgen -W "${box_list}" -- ${cur})) return 0 ;; "up") + vagrant_state_file=$(__vagrantinvestigate) || return 1 + if [[ -d $vagrant_state_file ]] + then + local vm_list=$(find $vagrant_state_file/machines -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) + fi local up_commands="--no-provision" - COMPREPLY=($(compgen -W "${up_commands}" -- ${cur})) + COMPREPLY=($(compgen -W "${up_commands} ${vm_list}" -- ${cur})) return 0 ;; "ssh"|"provision"|"reload"|"halt"|"suspend"|"resume"|"ssh-config") @@ -107,18 +112,32 @@ _vagrant() { if [ $COMP_CWORD == 3 ] then action="${COMP_WORDS[COMP_CWORD-2]}" - if [ $action == 'box' ] - then - case "$prev" in + case "$action" in + "up") + if [ "$prev" == "--no-provision" ] + then + if [[ -d $vagrant_state_file ]] + then + local vm_list=$(find $vagrant_state_file/machines -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) + fi + COMPREPLY=($(compgen -W "${vm_list}" -- ${cur})) + return 0 + fi + ;; + "box") + case "$prev" in "remove"|"repackage") - local box_list=$(find $HOME/.vagrant.d/boxes -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) + local box_list=$(find "${VAGRANT_HOME:-${HOME}/.vagrant.d}/boxes" -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) COMPREPLY=($(compgen -W "${box_list}" -- ${cur})) return 0 ;; *) - ;; - esac - fi + ;; + esac + ;; + *) + ;; + esac fi } diff --git a/contrib/sudoers/linux-fedora b/contrib/sudoers/linux-fedora new file mode 100644 index 000000000..f2d64b66d --- /dev/null +++ b/contrib/sudoers/linux-fedora @@ -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 diff --git a/contrib/sudoers/linux b/contrib/sudoers/linux-ubuntu similarity index 100% rename from contrib/sudoers/linux rename to contrib/sudoers/linux-ubuntu diff --git a/lib/vagrant.rb b/lib/vagrant.rb index 26ed01820..a84bb1b05 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -123,6 +123,7 @@ module Vagrant c.register([:"2", :host]) { Plugin::V2::Host } c.register([:"2", :provider]) { Plugin::V2::Provider } c.register([:"2", :provisioner]) { Plugin::V2::Provisioner } + c.register([:"2", :push]) { Plugin::V2::Push } c.register([:"2", :synced_folder]) { Plugin::V2::SyncedFolder } end @@ -138,16 +139,27 @@ module Vagrant Config.run(version, &block) end - # This checks if a plugin with the given name is installed. This can - # be used from the Vagrantfile to easily branch based on plugin - # availability. - def self.has_plugin?(name) - # We check the plugin names first because those are cheaper to check - return true if plugin("2").manager.registered.any? { |p| p.name == name } + # This checks if a plugin with the given name is available (installed + # and enabled). This can be used from the Vagrantfile to easily branch + # based on plugin availability. + def self.has_plugin?(name, version=nil) + return false unless Vagrant.plugins_enabled? + + if !version + # We check the plugin names first because those are cheaper to check + return true if plugin("2").manager.registered.any? { |p| p.name == name } + end + + # Make the requirement object + version = Gem::Requirement.new([version]) if version # Now check the plugin gem names require "vagrant/plugin/manager" - Plugin::Manager.instance.installed_specs.any? { |s| s.name == name } + Plugin::Manager.instance.installed_specs.any? do |s| + match = s.name == name + next match if !version + next match && version.satisfied_by?(s.version) + end end # Returns a superclass to use when creating a plugin for Vagrant. @@ -215,6 +227,22 @@ module Vagrant requirements: requirements.join(", "), version: VERSION end + + # This allows plugin developers to access the original environment before + # Vagrant even ran. This is useful when shelling out, especially to other + # Ruby processes. + # + # @return [Hash] + def self.original_env + {}.tap do |h| + ENV.each do |k,v| + if k.start_with?("VAGRANT_OLD_ENV") + key = k.sub(/^VAGRANT_OLD_ENV_/, "") + h[key] = v + end + end + end + end end # Default I18n to load the en locale @@ -260,7 +288,7 @@ end if Vagrant.plugins_enabled? begin global_logger.info("Loading plugins!") - Bundler.require(:plugins) + $vagrant_bundler_runtime.require(:plugins) rescue Exception => e raise Vagrant::Errors::PluginLoadError, message: e.to_s end diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index fb2109326..6aec780ee 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -115,6 +115,8 @@ module Vagrant # @param [Array] urls # @param [Hash] env def add_direct(urls, env) + env[:ui].output(I18n.t("vagrant.box_adding_direct")) + name = env[:box_name] if !name || name == "" raise Errors::BoxAddNameRequired @@ -147,7 +149,7 @@ module Vagrant # element is an authenticated URL. # @param [Hash] env # @param [Bool] expanded True if the metadata URL was expanded with - # a Vagrant Cloud server URL. + # a Atlas server URL. def add_from_metadata(url, env, expanded) original_url = env[:box_url] provider = env[:box_provider] @@ -174,6 +176,7 @@ module Vagrant begin metadata_path = download( authenticated_url, env, json: true, ui: false) + return if @download_interrupted File.open(metadata_path) do |f| metadata = BoxMetadata.new(f) @@ -369,7 +372,7 @@ module Vagrant # # @return [Hash] def downloader(url, env, **opts) - opts[:ui] = true if !opts.has_key?(:ui) + opts[:ui] = true if !opts.key?(:ui) temp_path = env[:tmp_path].join("box" + Digest::SHA1.hexdigest(url)) @logger.info("Downloading box: #{url} => #{temp_path}") @@ -401,15 +404,16 @@ module Vagrant downloader_options[:ca_path] = env[:box_download_ca_path] downloader_options[:continue] = true downloader_options[:insecure] = env[:box_download_insecure] - downloader_options[:client_cert] = env[:box_client_cert] + downloader_options[:client_cert] = env[:box_download_client_cert] downloader_options[:headers] = ["Accept: application/json"] if opts[:json] downloader_options[:ui] = env[:ui] if opts[:ui] + downloader_options[:location_trusted] = env[:box_download_location_trusted] Util::Downloader.new(url, temp_path, downloader_options) end def download(url, env, **opts) - opts[:ui] = true if !opts.has_key?(:ui) + opts[:ui] = true if !opts.key?(:ui) d = downloader(url, env, **opts) @@ -420,8 +424,15 @@ module Vagrant show_url = opts[:show_url] show_url ||= url + translation = "vagrant.box_downloading" + + # Adjust status message when 'downloading' a local box. + if show_url.start_with?("file://") + translation = "vagrant.box_unpacking" + end + env[:ui].detail(I18n.t( - "vagrant.box_downloading", + translation, url: show_url)) if File.file?(d.destination) env[:ui].info(I18n.t("vagrant.actions.box.download.resuming")) @@ -483,7 +494,7 @@ module Vagrant output = d.head match = output.scan(/^Content-Type: (.+?)$/i).last return false if !match - match.last.chomp == "application/json" + !!(match.last.chomp =~ /application\/json/) end def validate_checksum(checksum_type, checksum, path) diff --git a/lib/vagrant/action/builtin/config_validate.rb b/lib/vagrant/action/builtin/config_validate.rb index e4fd44cdd..3c4461a10 100644 --- a/lib/vagrant/action/builtin/config_validate.rb +++ b/lib/vagrant/action/builtin/config_validate.rb @@ -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? diff --git a/lib/vagrant/action/builtin/graceful_halt.rb b/lib/vagrant/action/builtin/graceful_halt.rb index 393c81398..33f6cfb9b 100644 --- a/lib/vagrant/action/builtin/graceful_halt.rb +++ b/lib/vagrant/action/builtin/graceful_halt.rb @@ -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 diff --git a/lib/vagrant/action/builtin/handle_box.rb b/lib/vagrant/action/builtin/handle_box.rb index 0ecaece67..5d7f6cf68 100644 --- a/lib/vagrant/action/builtin/handle_box.rb +++ b/lib/vagrant/action/builtin/handle_box.rb @@ -64,6 +64,9 @@ module Vagrant box_download_ca_path = machine.config.vm.box_download_ca_path box_download_client_cert = machine.config.vm.box_download_client_cert box_download_insecure = machine.config.vm.box_download_insecure + box_download_checksum_type = machine.config.vm.box_download_checksum_type + box_download_checksum = machine.config.vm.box_download_checksum + box_download_location_trusted = machine.config.vm.box_download_location_trusted box_formats = machine.provider_options[:box_format] || machine.provider_name @@ -79,12 +82,16 @@ module Vagrant env[:action_runner].run(Vagrant::Action.action_box_add, env.merge({ box_name: machine.config.vm.box, box_url: machine.config.vm.box_url || machine.config.vm.box, + box_server_url: machine.config.vm.box_server_url, box_provider: box_formats, box_version: machine.config.vm.box_version, - box_client_cert: box_download_client_cert, + box_download_client_cert: box_download_client_cert, box_download_ca_cert: box_download_ca_cert, box_download_ca_path: box_download_ca_path, box_download_insecure: box_download_insecure, + box_checksum_type: box_download_checksum_type, + box_checksum: box_download_checksum, + box_download_location_trusted: box_download_location_trusted, })) rescue Errors::BoxAlreadyExists # Just ignore this, since it means the next part will succeed! diff --git a/lib/vagrant/action/builtin/mixin_provisioners.rb b/lib/vagrant/action/builtin/mixin_provisioners.rb index 9ee465aa5..3d712c954 100644 --- a/lib/vagrant/action/builtin/mixin_provisioners.rb +++ b/lib/vagrant/action/builtin/mixin_provisioners.rb @@ -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 diff --git a/lib/vagrant/action/builtin/mixin_synced_folders.rb b/lib/vagrant/action/builtin/mixin_synced_folders.rb index 42e3a290f..fb0abcce2 100644 --- a/lib/vagrant/action/builtin/mixin_synced_folders.rb +++ b/lib/vagrant/action/builtin/mixin_synced_folders.rb @@ -25,6 +25,15 @@ module Vagrant # Order the plugins by priority. Higher is tried before lower. ordered = ordered.sort { |a, b| b[0] <=> a[0] } + allowed_types = machine.config.vm.allowed_synced_folder_types + if allowed_types + ordered = allowed_types.map do |type| + ordered.find do |_, key, impl| + key == type + end + end.compact + end + # Find the proper implementation ordered.each do |_, key, impl| return key if impl.new.usable?(machine) @@ -93,11 +102,12 @@ module Vagrant config = opts[:config] config ||= machine.config.vm + config_folders = config.synced_folders folders = {} # Determine all the synced folders as well as the implementation # they're going to use. - config.synced_folders.each do |id, data| + config_folders.each do |id, data| # Ignore disabled synced folders next if data[:disabled] @@ -112,10 +122,12 @@ module Vagrant raise "Internal error. Report this as a bug. Invalid: #{data[:type]}" end - if !impl_class[0].new.usable?(machine, true) - # Verify that explicitly defined shared folder types are - # actually usable. - raise Errors::SyncedFolderUnusable, type: data[:type].to_s + if !opts[:disable_usable_check] + if !impl_class[0].new.usable?(machine, true) + # Verify that explicitly defined shared folder types are + # actually usable. + raise Errors::SyncedFolderUnusable, type: data[:type].to_s + end end end @@ -126,7 +138,7 @@ module Vagrant # If we have folders with the "default" key, then determine the # most appropriate implementation for this. - if folders.has_key?("") && !folders[""].empty? + if folders.key?("") && !folders[""].empty? default_impl = default_synced_folder_type(machine, plugins) if !default_impl types = plugins.to_hash.keys.map { |t| t.to_s }.sort.join(", ") diff --git a/lib/vagrant/action/builtin/provision.rb b/lib/vagrant/action/builtin/provision.rb index 0795a2ddc..02868c735 100644 --- a/lib/vagrant/action/builtin/provision.rb +++ b/lib/vagrant/action/builtin/provision.rb @@ -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), diff --git a/lib/vagrant/action/builtin/ssh_exec.rb b/lib/vagrant/action/builtin/ssh_exec.rb index 23b209066..81ae53cd6 100644 --- a/lib/vagrant/action/builtin/ssh_exec.rb +++ b/lib/vagrant/action/builtin/ssh_exec.rb @@ -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 diff --git a/lib/vagrant/action/builtin/ssh_run.rb b/lib/vagrant/action/builtin/ssh_run.rb index db2a0d79e..d4058e6e4 100644 --- a/lib/vagrant/action/builtin/ssh_run.rb +++ b/lib/vagrant/action/builtin/ssh_run.rb @@ -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 diff --git a/lib/vagrant/action/builtin/synced_folders.rb b/lib/vagrant/action/builtin/synced_folders.rb index aef6d0e71..230676d66 100644 --- a/lib/vagrant/action/builtin/synced_folders.rb +++ b/lib/vagrant/action/builtin/synced_folders.rb @@ -76,8 +76,10 @@ module Vagrant # Go through each folder and prepare the folders folders.each do |impl, impl_name, fs| - @logger.info("Invoking synced folder prepare for: #{impl_name}") - impl.prepare(env[:machine], fs, impl_opts(impl_name, env)) + if !env[:synced_folders_disable] + @logger.info("Invoking synced folder prepare for: #{impl_name}") + impl.prepare(env[:machine], fs, impl_opts(impl_name, env)) + end end # Continue, we need the VM to be booted. diff --git a/lib/vagrant/action/general/package.rb b/lib/vagrant/action/general/package.rb index ee7e30ec3..58991222c 100644 --- a/lib/vagrant/action/general/package.rb +++ b/lib/vagrant/action/general/package.rb @@ -1,4 +1,5 @@ require 'fileutils' +require "pathname" require 'vagrant/util/safe_chdir' require 'vagrant/util/subprocess' @@ -28,14 +29,18 @@ module Vagrant def call(env) @env = env - + file_name = File.basename(@env["package.output"].to_s) + raise Errors::PackageOutputDirectory if File.directory?(tar_path) - raise Errors::PackageOutputExists if File.exist?(tar_path) + raise Errors::PackageOutputExists, file_name:file_name if File.exist?(tar_path) raise Errors::PackageRequiresDirectory if !env["package.directory"] || !File.directory?(env["package.directory"]) @app.call(env) + @env[:ui].info I18n.t("vagrant.actions.general.package.compressing", tar_path: tar_path) + copy_include_files + setup_private_key compress end @@ -81,11 +86,6 @@ module Vagrant # Compress the exported file into a package def compress - @env[:ui].info I18n.t("vagrant.actions.general.package.compressing", tar_path: tar_path) - - # Copy over the included files - copy_include_files - # Get the output path. We have to do this up here so that the # pwd returns the proper thing. output_path = tar_path.to_s @@ -100,6 +100,39 @@ module Vagrant end end + # This will copy the generated private key into the box and use + # it for SSH by default. We have to do this because we now generate + # random keypairs on boot, so packaged boxes would stop working + # without this. + def setup_private_key + # If we don't have machine, we do nothing (weird) + return if !@env[:machine] + + # If we don't have a data dir, we also do nothing (base package) + return if !@env[:machine].data_dir + + # If we don't have a generated private key, we do nothing + path = @env[:machine].data_dir.join("private_key") + return if !path.file? + + # Copy it into our box directory + dir = Pathname.new(@env["package.directory"]) + new_path = dir.join("vagrant_private_key") + FileUtils.cp(path, new_path) + + # Append it to the Vagrantfile (or create a Vagrantfile) + vf_path = dir.join("Vagrantfile") + mode = "w+" + mode = "a" if vf_path.file? + vf_path.open(mode) do |f| + f.binmode + f.puts + f.puts %Q[Vagrant.configure("2") do |config|] + f.puts %Q[ config.ssh.private_key_path = File.expand_path("../vagrant_private_key", __FILE__)] + f.puts %Q[end] + end + end + # Path to the final box output file def tar_path File.expand_path(@env["package.output"], FileUtils.pwd) diff --git a/lib/vagrant/action/runner.rb b/lib/vagrant/action/runner.rb index 6d4a2e409..8799a8d7f 100644 --- a/lib/vagrant/action/runner.rb +++ b/lib/vagrant/action/runner.rb @@ -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 diff --git a/lib/vagrant/box.rb b/lib/vagrant/box.rb index 537c94e2a..ce829870a 100644 --- a/lib/vagrant/box.rb +++ b/lib/vagrant/box.rb @@ -173,7 +173,7 @@ module Vagrant Util::SafeChdir.safe_chdir(@directory) do # Find all the files in our current directory and tar it up! - files = Dir.glob(File.join(".", "**", "*")) + files = Dir.glob(File.join(".", "**", "*")).select { |f| File.file?(f) } # Package! Util::Subprocess.execute("bsdtar", "-czf", path.to_s, *files) diff --git a/lib/vagrant/box_collection.rb b/lib/vagrant/box_collection.rb index cfefbf030..a7599f7c2 100644 --- a/lib/vagrant/box_collection.rb +++ b/lib/vagrant/box_collection.rb @@ -29,7 +29,7 @@ module Vagrant # * BOX_NAME - The name of the box. This is a logical name given by # the user of Vagrant. # * PROVIDER - The provider that the box was built for (VirtualBox, - # VMWare, etc.). + # VMware, etc.). # * metadata.json - A simple JSON file that at the bare minimum # contains a "provider" key that matches the provider for the # box. This metadata JSON, however, can contain anything. @@ -225,6 +225,7 @@ module Vagrant # we have. child.children(true).each do |versiondir| next if !versiondir.directory? + next if versiondir.basename.to_s.start_with?(".") version = versiondir.basename.to_s @@ -270,6 +271,8 @@ module Vagrant versions = box_directory.children(true).map do |versiondir| next if !versiondir.directory? + next if versiondir.basename.to_s.start_with?(".") + version = versiondir.basename.to_s Gem::Version.new(version) end.compact diff --git a/lib/vagrant/capability_host.rb b/lib/vagrant/capability_host.rb index e3b579a49..47491deef 100644 --- a/lib/vagrant/capability_host.rb +++ b/lib/vagrant/capability_host.rb @@ -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 diff --git a/lib/vagrant/config/loader.rb b/lib/vagrant/config/loader.rb index f57b5d31e..e3f80a579 100644 --- a/lib/vagrant/config/loader.rb +++ b/lib/vagrant/config/loader.rb @@ -49,7 +49,7 @@ module Vagrant # Gather the procs for every source, since that is what we care about. procs = [] sources.each do |source| - if !@proc_cache.has_key?(source) + if !@proc_cache.key?(source) # Load the procs for this source and cache them. This caching # avoids the issue where a file may have side effects when loading # and loading it multiple times causes unexpected behavior. @@ -92,10 +92,10 @@ module Vagrant errors = [] order.each do |key| - next if !@sources.has_key?(key) + next if !@sources.key?(key) @sources[key].each do |version, proc| - if !@config_cache.has_key?(proc) + if !@config_cache.key?(proc) @logger.debug("Loading from: #{key} (evaluating)") # Get the proper version loader for this version and load @@ -209,9 +209,16 @@ module Vagrant @logger.error("Vagrantfile load error: #{e.message}") @logger.error(e.backtrace.join("\n")) + line = "(unknown)" + if e.backtrace && e.backtrace[0] + line = e.backtrace[0].split(":")[1] + end + # Report the generic exception raise Errors::VagrantfileLoadError, path: path, + line: line, + exception_class: e.class, message: e.message end end diff --git a/lib/vagrant/config/v1/loader.rb b/lib/vagrant/config/v1/loader.rb index 910ae3dda..e26c88e44 100644 --- a/lib/vagrant/config/v1/loader.rb +++ b/lib/vagrant/config/v1/loader.rb @@ -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 diff --git a/lib/vagrant/config/v1/root.rb b/lib/vagrant/config/v1/root.rb index 1f7db9763..975a9e4d3 100644 --- a/lib/vagrant/config/v1/root.rb +++ b/lib/vagrant/config/v1/root.rb @@ -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 diff --git a/lib/vagrant/config/v2/loader.rb b/lib/vagrant/config/v2/loader.rb index 1c3bc0224..2f5b6cbac 100644 --- a/lib/vagrant/config/v2/loader.rb +++ b/lib/vagrant/config/v2/loader.rb @@ -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 diff --git a/lib/vagrant/config/v2/root.rb b/lib/vagrant/config/v2/root.rb index f64129dad..f4a18c10b 100644 --- a/lib/vagrant/config/v2/root.rb +++ b/lib/vagrant/config/v2/root.rb @@ -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 diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 455921485..d4b8db1fd 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -80,7 +80,7 @@ module Vagrant }.merge(opts || {}) # Set the default working directory to look for the vagrantfile - opts[:cwd] ||= ENV["VAGRANT_CWD"] if ENV.has_key?("VAGRANT_CWD") + opts[:cwd] ||= ENV["VAGRANT_CWD"] if ENV.key?("VAGRANT_CWD") opts[:cwd] ||= Dir.pwd opts[:cwd] = Pathname.new(opts[:cwd]) if !opts[:cwd].directory? @@ -94,7 +94,7 @@ module Vagrant # Set the Vagrantfile name up. We append "Vagrantfile" and "vagrantfile" so that # those continue to work as well, but anything custom will take precedence. opts[:vagrantfile_name] ||= ENV["VAGRANT_VAGRANTFILE"] if \ - ENV.has_key?("VAGRANT_VAGRANTFILE") + ENV.key?("VAGRANT_VAGRANTFILE") opts[:vagrantfile_name] = [opts[:vagrantfile_name]] if \ opts[:vagrantfile_name] && !opts[:vagrantfile_name].is_a?(Array) @@ -164,6 +164,15 @@ module Vagrant @local_data_path = Pathname.new(File.expand_path(opts[:local_data_path], @cwd)) end + # If we have a root path, load the ".vagrantplugins" file. + if root_path + plugins_file = root_path.join(".vagrantplugins") + if plugins_file.file? + @logger.info("Loading plugins file: #{plugins_file}") + load plugins_file + end + end + setup_local_data_path # Setup the default private key @@ -298,7 +307,7 @@ module Vagrant # @return [Symbol] Name of the default provider. def default_provider(**opts) opts[:exclude] = Set.new(opts[:exclude]) if opts[:exclude] - opts[:force_default] = true if !opts.has_key?(:force_default) + opts[:force_default] = true if !opts.key?(:force_default) default = ENV["VAGRANT_DEFAULT_PROVIDER"] default = nil if default == "" @@ -308,6 +317,30 @@ module Vagrant # that (the default behavior) return default if default && opts[:force_default] + # Determine the config to use to look for provider definitions. By + # default it is the global but if we're targeting a specific machine, + # then look there. + root_config = vagrantfile.config + if opts[:machine] + machine_info = vagrantfile.machine_config(opts[:machine], nil, nil) + root_config = machine_info[:config] + end + + # Get the list of providers within our configuration and assign + # a priority to each in the order they exist so that we try these first. + config = {} + root_config.vm.__providers.reverse.each_with_index do |key, idx| + config[key] = idx + end + + # Determine the max priority so that we can add the config priority + # to that to make sure it is highest. + max_priority = 0 + Vagrant.plugin("2").manager.providers.each do |key, data| + priority = data[1][:priority] + max_priority = priority if priority > max_priority + end + ordered = [] Vagrant.plugin("2").manager.providers.each do |key, data| impl = data[0] @@ -316,10 +349,19 @@ module Vagrant # Skip excluded providers next if opts[:exclude] && opts[:exclude].include?(key) - # Skip providers that can't be defaulted - next if popts.has_key?(:defaultable) && !popts[:defaultable] + # Skip providers that can't be defaulted, unless they're in our + # config, in which case someone made our decision for us. + if !config.key?(key) + next if popts.key?(:defaultable) && !popts[:defaultable] + end - ordered << [popts[:priority], key, impl, popts] + # The priority is higher if it is in our config. Otherwise, it is + # the priority it set PLUS the length of the config to make sure it + # is never higher than the configuration keys. + priority = popts[:priority] + priority = config[key] + max_priority if config.key?(key) + + ordered << [priority, key, impl, popts] end # Order the providers by priority. Higher values are tried first. @@ -336,8 +378,8 @@ module Vagrant return key if impl.usable?(false) end - # If all else fails, return VirtualBox - return :virtualbox + # No providers available is a critical error for Vagrant. + raise Errors::NoDefaultProvider end # Returns the collection of boxes for the environment. @@ -501,6 +543,41 @@ module Vagrant end end + # This executes the push with the given name, raising any exceptions that + # occur. + # + # Precondition: the push is not nil and exists. + def push(name) + @logger.info("Getting push: #{name}") + + name = name.to_sym + + pushes = self.vagrantfile.config.push.__compiled_pushes + if !pushes.key?(name) + raise Vagrant::Errors::PushStrategyNotDefined, + name: name, + pushes: pushes.keys + end + + strategy, config = pushes[name] + push_registry = Vagrant.plugin("2").manager.pushes + klass, _ = push_registry.get(strategy) + if klass.nil? + raise Vagrant::Errors::PushStrategyNotLoaded, + name: strategy, + pushes: push_registry.keys + end + + klass.new(self, config).push + end + + # The list of pushes defined in this Vagrantfile. + # + # @return [Array] + def pushes + self.vagrantfile.config.push.__compiled_pushes.keys + end + # This returns a machine with the proper provider for this environment. # The machine named by `name` must be in this environment. # @@ -523,7 +600,7 @@ module Vagrant @machines.delete(cache_key) end - if @machines.has_key?(cache_key) + if @machines.key?(cache_key) @logger.info("Returning cached machine: #{name} (#{provider})") return @machines[cache_key] end @@ -531,10 +608,8 @@ module Vagrant @logger.info("Uncached load of machine.") # Determine the machine data directory and pass it to the machine. - # XXX: Permissions error here. machine_data_path = @local_data_path.join( "machines/#{name}/#{provider}") - FileUtils.mkdir_p(machine_data_path) # Create the machine and cache it for future calls. This will also # return the machine from this method. @@ -786,7 +861,10 @@ module Vagrant # This upgrades a home directory that was in the v1.1 format to the # v1.5 format. It will raise exceptions if anything fails. def upgrade_home_path_v1_1 - @ui.ask(I18n.t("vagrant.upgrading_home_path_v1_5")) + if !ENV["VAGRANT_UPGRADE_SILENT_1_5"] + @ui.ask(I18n.t("vagrant.upgrading_home_path_v1_5")) + end + collection = BoxCollection.new( @home_path.join("boxes"), temp_dir_root: tmp_path) collection.upgrade_v1_1_v1_5 diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index b4a609aa1..2ce133618 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -316,6 +316,10 @@ module Vagrant error_key(:corrupt_machine_index) end + class DarwinMountFailed < VagrantError + error_key(:darwin_mount_failed) + end + class DarwinNFSMountFailed < VagrantError error_key(:darwin_nfs_mount_failed) end @@ -476,6 +480,10 @@ module Vagrant error_key(:nfs_client_not_installed_in_guest) end + class NoDefaultProvider < VagrantError + error_key(:no_default_provider) + end + class NoDefaultSyncedFolderImpl < VagrantError error_key(:no_default_synced_folder_impl) end @@ -552,6 +560,22 @@ module Vagrant error_key(:plugin_uninstall_system) end + class PushesNotDefined < VagrantError + error_key(:pushes_not_defined) + end + + class PushStrategyNotDefined < VagrantError + error_key(:push_strategy_not_defined) + end + + class PushStrategyNotLoaded < VagrantError + error_key(:push_strategy_not_loaded) + end + + class PushStrategyNotProvided < VagrantError + error_key(:push_strategy_not_provided) + end + class RSyncError < VagrantError error_key(:rsync_error) end @@ -616,6 +640,10 @@ module Vagrant error_key(:ssh_invalid_shell) end + class SSHInsertKeyUnsupported < VagrantError + error_key(:ssh_insert_key_unsupported) + end + class SSHIsPuttyLink < VagrantError error_key(:ssh_is_putty_link) end @@ -692,6 +720,10 @@ module Vagrant error_key(:vboxmanage_error) end + class VBoxManageLaunchError < VagrantError + error_key(:vboxmanage_launch_error) + end + class VBoxManageNotFoundError < VagrantError error_key(:vboxmanage_not_found_error) end @@ -728,6 +760,18 @@ module Vagrant error_key(:virtualbox_no_name) end + class VirtualBoxNameExists < VagrantError + error_key(:virtualbox_name_exists) + end + + class VirtualBoxUserMismatch < VagrantError + error_key(:virtualbox_user_mismatch) + end + + class VirtualBoxVersionEmpty < VagrantError + error_key(:virtualbox_version_empty) + end + class VMBaseMacNotSpecified < VagrantError error_key(:no_base_mac, "vagrant.actions.vm.match_mac") end diff --git a/lib/vagrant/machine.rb b/lib/vagrant/machine.rb index 9d2c0d15c..07ec0ac94 100644 --- a/lib/vagrant/machine.rb +++ b/lib/vagrant/machine.rb @@ -1,3 +1,5 @@ +require_relative "util/ssh" + require "digest/md5" require "thread" @@ -6,7 +8,7 @@ require "log4r" module Vagrant # This represents a machine that Vagrant manages. This provides a singular # API for querying the state and making state changes to the machine, which - # is backed by any sort of provider (VirtualBox, VMWare, etc.). + # is backed by any sort of provider (VirtualBox, VMware, etc.). class Machine # The box that is backing this machine. # @@ -133,6 +135,12 @@ module Vagrant @logger.debug("Eager loading WinRM communicator to avoid GH-3390") communicate end + + # If the ID is the special not created ID, then set our ID to + # nil so that we destroy all our data. + if state.id == MachineState::NOT_CREATED_ID + self.id = nil + end end # This calls an action on the provider. The provider may or may not @@ -142,20 +150,29 @@ module Vagrant # @param [Hash] extra_env This data will be passed into the action runner # as extra data set on the environment hash for the middleware # runner. - def action(name, extra_env=nil) + def action(name, opts=nil) @logger.info("Calling action: #{name} on provider #{@provider}") + opts ||= {} + + # Determine whether we lock or not + lock = true + lock = opts.delete(:lock) if opts.key?(:lock) + + # Extra env keys are the remaining opts + extra_env = opts.dup + # Create a deterministic ID for this machine vf = nil vf = @env.vagrantfile_name[0] if @env.vagrantfile_name id = Digest::MD5.hexdigest( - "#{@env.root_path}#{vf}#{@name}") + "#{@env.root_path}#{vf}#{@env.local_data_path}#{@name}") # We only lock if we're not executing an SSH action. In the future # we will want to do more fine-grained unlocking in actions themselves # but for a 1.6.2 release this will work. locker = Proc.new { |*args, &block| block.call } - locker = @env.method(:lock) if !name.to_s.start_with?("ssh") + locker = @env.method(:lock) if lock && !name.to_s.start_with?("ssh") # Lock this machine for the duration of this action locker.call("machine-action-#{id}") do @@ -170,6 +187,7 @@ module Vagrant provider: @provider.to_s end + # Call the action action_raw(name, callable, extra_env) end rescue Errors::EnvironmentLockedError @@ -261,6 +279,13 @@ module Vagrant end end + if uid_file + # Write the user id that created this machine + uid_file.open("w+") do |f| + f.write(Process.uid.to_s) + end + end + # If we don't have a UUID, then create one if index_uuid.nil? # Create the index entry and save it @@ -293,6 +318,7 @@ module Vagrant else # Delete the file, since the machine is now destroyed id_file.delete if id_file && id_file.file? + uid_file.delete if uid_file && uid_file.file? # If we have a UUID associated with the index, remove it uuid = index_uuid @@ -342,6 +368,7 @@ module Vagrant # This reloads the ID of the underlying machine. def reload + old_id = @id @id = nil if @data_dir @@ -350,6 +377,13 @@ module Vagrant id_file = @data_dir.join("id") @id = id_file.read.chomp if id_file.file? end + + if @id != old_id && @provider + # It changed, notify the provider + @provider.machine_id_changed + end + + @id end # This returns the SSH info for accessing this machine. This SSH info @@ -403,6 +437,8 @@ module Vagrant info[:forward_agent] = @config.ssh.forward_agent info[:forward_x11] = @config.ssh.forward_x11 + info[:ssh_command] = @config.ssh.ssh_command if @config.ssh.ssh_command + # Add in provided proxy command config info[:proxy_command] = @config.ssh.proxy_command if @config.ssh.proxy_command @@ -418,7 +454,7 @@ module Vagrant end # If we have a private key in our data dir, then use that - if @data_dir + if @data_dir && !@config.ssh.private_key_path data_private_key = @data_dir.join("private_key") if data_private_key.file? info[:private_key_path] = [data_private_key.to_s] @@ -434,6 +470,14 @@ module Vagrant File.expand_path(path, @env.root_path) end + # Check that the private key permissions are valid + info[:private_key_path].each do |path| + key_path = Pathname.new(path) + if key_path.exist? + Vagrant::Util::SSH.check_key_permissions(key_path) + end + end + # Return the final compiled SSH info data info end @@ -446,12 +490,6 @@ module Vagrant result = @provider.state raise Errors::MachineStateInvalid if !result.is_a?(MachineState) - # If the ID is the special not created ID, then set our ID to - # nil so that we destroy all our data. - if result.id == MachineState::NOT_CREATED_ID - self.id = nil - end - # Update our state cache if we have a UUID and an entry in the # master index. uuid = index_uuid @@ -467,6 +505,17 @@ module Vagrant result end + # Returns the user ID that created this machine. This is specific to + # the host machine that this was created on. + # + # @return [String] + def uid + path = uid_file + return nil if !path + return nil if !path.file? + return uid_file.read.chomp + end + # Temporarily changes the machine UI. This is useful if you want # to execute an {#action} with a different UI. def with_ui(ui) @@ -480,5 +529,13 @@ module Vagrant end end end + + protected + + # Returns the path to the file that stores the UID. + def uid_file + return nil if !@data_dir + @data_dir.join("creator_uid") + end end end diff --git a/lib/vagrant/plugin/manager.rb b/lib/vagrant/plugin/manager.rb index 0cd71c8ba..dfaab2ecf 100644 --- a/lib/vagrant/plugin/manager.rb +++ b/lib/vagrant/plugin/manager.rb @@ -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 diff --git a/lib/vagrant/plugin/state_file.rb b/lib/vagrant/plugin/state_file.rb index 4a39a52bc..f41da1b86 100644 --- a/lib/vagrant/plugin/state_file.rb +++ b/lib/vagrant/plugin/state_file.rb @@ -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. diff --git a/lib/vagrant/plugin/v2.rb b/lib/vagrant/plugin/v2.rb index 1539667bd..953f73ff6 100644 --- a/lib/vagrant/plugin/v2.rb +++ b/lib/vagrant/plugin/v2.rb @@ -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 diff --git a/lib/vagrant/plugin/v2/command.rb b/lib/vagrant/plugin/v2/command.rb index a702b767c..eeb580a29 100644 --- a/lib/vagrant/plugin/v2/command.rb +++ b/lib/vagrant/plugin/v2/command.rb @@ -58,7 +58,7 @@ module Vagrant opts.parse!(argv) return argv - rescue OptionParser::InvalidOption + rescue OptionParser::InvalidOption, OptionParser::MissingArgument raise Errors::CLIInvalidOptions, help: opts.help.chomp end @@ -162,7 +162,7 @@ module Vagrant end # Use the default provider if nothing else - provider_to_use ||= @env.default_provider + provider_to_use ||= @env.default_provider(machine: name) # Get the right machine with the right provider @env.machine(name, provider_to_use) @@ -224,6 +224,15 @@ module Vagrant @logger.info("With machine: #{machine.name} (#{machine.provider.inspect})") yield machine + + # Call the state method so that we update our index state. Don't + # worry about exceptions here, since we just care about updating + # the cache. + begin + # Called for side effects + machine.state + rescue Errors::VagrantError + end end end diff --git a/lib/vagrant/plugin/v2/components.rb b/lib/vagrant/plugin/v2/components.rb index 7bae6c29a..d7c64d370 100644 --- a/lib/vagrant/plugin/v2/components.rb +++ b/lib/vagrant/plugin/v2/components.rb @@ -54,6 +54,11 @@ module Vagrant # @return [Hash] attr_reader :provider_capabilities + # This contains all the push implementations by name. + # + # @return [Registry>] + attr_reader :pushes + # This contains all the synced folder implementations by name. # # @return [Registry>] @@ -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 diff --git a/lib/vagrant/plugin/v2/manager.rb b/lib/vagrant/plugin/v2/manager.rb index 62a23f82a..ce76d1fc1 100644 --- a/lib/vagrant/plugin/v2/manager.rb +++ b/lib/vagrant/plugin/v2/manager.rb @@ -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] diff --git a/lib/vagrant/plugin/v2/plugin.rb b/lib/vagrant/plugin/v2/plugin.rb index 9a7f6177d..025032fd9 100644 --- a/lib/vagrant/plugin/v2/plugin.rb +++ b/lib/vagrant/plugin/v2/plugin.rb @@ -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. diff --git a/lib/vagrant/plugin/v2/push.rb b/lib/vagrant/plugin/v2/push.rb new file mode 100644 index 000000000..f8bc15d53 --- /dev/null +++ b/lib/vagrant/plugin/v2/push.rb @@ -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 diff --git a/lib/vagrant/registry.rb b/lib/vagrant/registry.rb index 5095f2097..d84a745a5 100644 --- a/lib/vagrant/registry.rb +++ b/lib/vagrant/registry.rb @@ -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. diff --git a/lib/vagrant/shared_helpers.rb b/lib/vagrant/shared_helpers.rb index c2c52ae10..fe114013e 100644 --- a/lib/vagrant/shared_helpers.rb +++ b/lib/vagrant/shared_helpers.rb @@ -5,12 +5,12 @@ require "thread" module Vagrant @@global_lock = Mutex.new - # This is the default endpoint of the Vagrant Cloud in + # This is the default endpoint of the Atlas in # use. API calls will be made to this for various functions # of Vagrant that may require remote access. # # @return [String] - DEFAULT_SERVER_URL = "https://vagrantcloud.com" + DEFAULT_SERVER_URL = "https://atlas.hashicorp.com" # This holds a global lock for the duration of the block. This should # be invoked around anything that is modifying process state (such as @@ -38,11 +38,11 @@ module Vagrant ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"] end - # This returns whether or not 3rd party plugins should be loaded. + # This returns whether or not 3rd party plugins should and can be loaded. # # @return [Boolean] def self.plugins_enabled? - !ENV["VAGRANT_NO_PLUGINS"] + !ENV["VAGRANT_NO_PLUGINS"] && $vagrant_bundler_runtime end # Whether or not super quiet mode is enabled. This is ill-advised. @@ -52,6 +52,13 @@ module Vagrant !!ENV["VAGRANT_I_KNOW_WHAT_IM_DOING_PLEASE_BE_QUIET"] end + # The current log level for Vagrant + # + # @return [String] + def self.log_level + ENV["VAGRANT_LOG"] + end + # Returns the URL prefix to the server. # # @return [String] diff --git a/lib/vagrant/ui.rb b/lib/vagrant/ui.rb index 4b47c3e1f..ba982bc05 100644 --- a/lib/vagrant/ui.rb +++ b/lib/vagrant/ui.rb @@ -21,9 +21,22 @@ module Vagrant # specific. See the implementation for more docs. attr_accessor :opts + # @return [IO] UI input. Defaults to `$stdin`. + attr_accessor :stdin + + # @return [IO] UI output. Defaults to `$stdout`. + attr_accessor :stdout + + # @return [IO] UI error output. Defaults to `$stderr`. + attr_accessor :stderr + def initialize @logger = Log4r::Logger.new("vagrant::ui::interface") @opts = {} + + @stdin = $stdin + @stdout = $stdout + @stderr = $stderr end def initialize_copy(original) @@ -50,6 +63,11 @@ module Vagrant define_method(method) { |*args| } end + # @return [false] + def color? + return false + end + # For machine-readable output. # # @param [String] type The type of the data @@ -132,23 +150,23 @@ module Vagrant super(message) # We can't ask questions when the output isn't a TTY. - raise Errors::UIExpectsTTY if !$stdin.tty? && !Vagrant::Util::Platform.cygwin? + raise Errors::UIExpectsTTY if !@stdin.tty? && !Vagrant::Util::Platform.cygwin? # Setup the options so that the new line is suppressed opts ||= {} - opts[:echo] = true if !opts.has_key?(:echo) - opts[:new_line] = false if !opts.has_key?(:new_line) - opts[:prefix] = false if !opts.has_key?(:prefix) + opts[:echo] = true if !opts.key?(:echo) + opts[:new_line] = false if !opts.key?(:new_line) + opts[:prefix] = false if !opts.key?(:prefix) # Output the data say(:info, message, opts) input = nil - if opts[:echo] - input = $stdin.gets + if opts[:echo] || !@stdin.respond_to?(:noecho) + input = @stdin.gets else begin - input = $stdin.noecho(&:gets) + input = @stdin.noecho(&:gets) # Output a newline because without echo, the newline isn't # echoed either. @@ -206,7 +224,7 @@ module Vagrant # Determine the proper IO channel to send this message # to based on the type of the message - channel = type == :error || opts[:channel] == :error ? $stderr : $stdout + channel = type == :error || opts[:channel] == :error ? @stderr : @stdout # Output! We wrap this in a lock so that it safely outputs only # one line at a time. We wrap this in a thread because as of Ruby 2.0 @@ -249,7 +267,7 @@ module Vagrant class_eval <<-CODE def #{method}(message, *args, **opts) super(message) - if !@ui.opts.has_key?(:bold) && !opts.has_key?(:bold) + if !@ui.opts.key?(:bold) && !opts.key?(:bold) opts[:bold] = #{method.inspect} != :detail && \ #{method.inspect} != :ask end @@ -284,7 +302,7 @@ module Vagrant opts = self.opts.merge(opts) prefix = "" - if !opts.has_key?(:prefix) || opts[:prefix] + if !opts.key?(:prefix) || opts[:prefix] prefix = OUTPUT_PREFIX prefix = " " * OUTPUT_PREFIX.length if \ type == :detail || type == :ask || opts[:prefix_spaces] @@ -294,7 +312,7 @@ module Vagrant return message if prefix.empty? target = @prefix - target = opts[:target] if opts.has_key?(:target) + target = opts[:target] if opts.key?(:target) # Get the lines. The first default is because if the message # is an empty string, then we want to still use the empty string. @@ -323,6 +341,11 @@ module Vagrant white: 37, } + # @return [true] + def color? + return true + end + # This is called by `say` to format the message for output. def format_message(type, message, **opts) # Get the format of the message before adding color. diff --git a/lib/vagrant/util.rb b/lib/vagrant/util.rb index db729bfdc..896b5d7e0 100644 --- a/lib/vagrant/util.rb +++ b/lib/vagrant/util.rb @@ -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' diff --git a/lib/vagrant/util/downloader.rb b/lib/vagrant/util/downloader.rb index 8756fc30f..a3ee5459e 100644 --- a/lib/vagrant/util/downloader.rb +++ b/lib/vagrant/util/downloader.rb @@ -29,8 +29,8 @@ module Vagrant begin url = URI.parse(@source) if url.scheme && url.scheme.start_with?("http") && url.user - auth = "#{url.user}" - auth += ":#{url.password}" if url.password + auth = "#{URI.unescape(url.user)}" + auth += ":#{URI.unescape(url.password)}" if url.password url.user = nil url.password = nil options[:auth] ||= auth @@ -49,6 +49,7 @@ module Vagrant @insecure = options[:insecure] @ui = options[:ui] @client_cert = options[:client_cert] + @location_trusted = options[:location_trusted] end # This executes the actual download, downloading the source file @@ -58,18 +59,15 @@ module Vagrant # If this method returns without an exception, the download # succeeded. An exception will be raised if the download failed. def download! - options, subprocess_options = self.options - options += ["--output", @destination] - options << @source - # This variable can contain the proc that'll be sent to # the subprocess execute. data_proc = nil + extra_subprocess_opts = {} if @ui # If we're outputting progress, then setup the subprocess to # tell us output so we can parse it out. - subprocess_options[:notify] = :stderr + extra_subprocess_opts[:notify] = :stderr progress_data = "" progress_regexp = /(\r(.+?))\r/ @@ -121,8 +119,31 @@ module Vagrant @logger.info(" -- Source: #{@source}") @logger.info(" -- Destination: #{@destination}") + retried = false begin + # Get the command line args and the subprocess opts based + # on our downloader settings. + options, subprocess_options = self.options + options += ["--output", @destination] + options << @source + + # Merge in any extra options we set + subprocess_options.merge!(extra_subprocess_opts) + + # Go! execute_curl(options, subprocess_options, &data_proc) + rescue Errors::DownloaderError => e + # If we already retried, raise it. + raise if retried + + # If its any error other than 33, it is an error. + raise if e.extra_data[:exit_code].to_i != 33 + + # Exit code 33 means that the server doesn't support ranges. + # In this case, try again without resume. + @continue = false + retried = true + retry ensure # If we're outputting to the UI, clear the output to # avoid lingering progress meters. @@ -177,7 +198,9 @@ module Vagrant @logger.warn("Downloader exit code: #{result.exit_code}") parts = result.stderr.split(/\n*curl:\s+\(\d+\)\s*/, 2) parts[1] ||= "" - raise Errors::DownloaderError, message: parts[1].chomp + raise Errors::DownloaderError, + code: result.exit_code, + message: parts[1].chomp end result @@ -202,6 +225,7 @@ module Vagrant options << "--insecure" if @insecure options << "--cert" << @client_cert if @client_cert options << "-u" << @auth if @auth + options << "--location-trusted" if @location_trusted if @headers Array(@headers).each do |header| diff --git a/lib/vagrant/util/env.rb b/lib/vagrant/util/env.rb new file mode 100644 index 000000000..689c9e5ac --- /dev/null +++ b/lib/vagrant/util/env.rb @@ -0,0 +1,51 @@ +require "bundler" + +module Vagrant + module Util + class Env + def self.with_original_env + original_env = ENV.to_hash + ENV.replace(::Bundler::ORIGINAL_ENV) if defined?(::Bundler::ORIGINAL_ENV) + ENV.update(Vagrant.original_env) + yield + ensure + ENV.replace(original_env.to_hash) + end + + # Execute the given command, removing any Ruby-specific environment + # variables. This is an "enhanced" version of `Bundler.with_clean_env`, + # which only removes Bundler-specific values. We need to remove all + # values, specifically: + # + # - _ORIGINAL_GEM_PATH + # - GEM_PATH + # - GEM_HOME + # - GEM_ROOT + # - BUNDLE_BIN_PATH + # - BUNDLE_GEMFILE + # - RUBYLIB + # - RUBYOPT + # - RUBY_ENGINE + # - RUBY_ROOT + # - RUBY_VERSION + # + # This will escape Vagrant's environment entirely, which is required if + # calling an executable that lives in another Ruby environment. The + # original environment restored at the end of this call. + # + # @param [Proc] block + # the block to execute with the cleaned environment + def self.with_clean_env + with_original_env do + ENV["MANPATH"] = ENV["BUNDLE_ORIG_MANPATH"] + ENV.delete_if { |k,_| k[0,7] == "BUNDLE_" } + if ENV.has_key? "RUBYOPT" + ENV["RUBYOPT"] = ENV["RUBYOPT"].sub("-rbundler/setup", "") + ENV["RUBYOPT"] = ENV["RUBYOPT"].sub("-I#{File.expand_path('..', __FILE__)}", "") + end + yield + end + end + end + end +end diff --git a/lib/vagrant/util/io.rb b/lib/vagrant/util/io.rb index b38bc3eef..c75257b9e 100644 --- a/lib/vagrant/util/io.rb +++ b/lib/vagrant/util/io.rb @@ -25,11 +25,11 @@ module Vagrant # We have to do this since `readpartial` will actually block # until data is available, which can cause blocking forever # in some cases. - results = ::IO.select([io], nil, nil, 0.1) + results = ::IO.select([io], nil, nil, 1.0) break if !results || results[0].empty? # Read! - data << io.readpartial(READ_CHUNK_SIZE) + data << io.readpartial(READ_CHUNK_SIZE).encode("UTF-8", Encoding.default_external) else # Do a simple non-blocking read on the IO object data << io.read_nonblock(READ_CHUNK_SIZE) diff --git a/lib/vagrant/util/is_port_open.rb b/lib/vagrant/util/is_port_open.rb index e98dd4c24..63652ee28 100644 --- a/lib/vagrant/util/is_port_open.rb +++ b/lib/vagrant/util/is_port_open.rb @@ -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 diff --git a/lib/vagrant/util/keypair.rb b/lib/vagrant/util/keypair.rb new file mode 100644 index 000000000..342d3264f --- /dev/null +++ b/lib/vagrant/util/keypair.rb @@ -0,0 +1,56 @@ +require "base64" +require "openssl" + +require "vagrant/util/retryable" + +module Vagrant + module Util + class Keypair + extend Retryable + + # Creates an SSH keypair and returns it. + # + # @param [String] password Password for the key, or nil for no password. + # @return [Array] PEM-encoded public and private key, + # respectively. The final element is the OpenSSH encoded public + # key. + def self.create(password=nil) + # This sometimes fails with RSAError. It is inconsistent and strangely + # sleeps seem to fix it. We just retry this a few times. See GH-5056 + rsa_key = nil + retryable(on: OpenSSL::PKey::RSAError, sleep: 2, tries: 5) do + rsa_key = OpenSSL::PKey::RSA.new(2048) + end + + public_key = rsa_key.public_key + private_key = rsa_key.to_pem + + if password + cipher = OpenSSL::Cipher::Cipher.new('des3') + private_key = rsa_key.to_pem(cipher, password) + end + + # Generate the binary necessary for the OpenSSH public key. + binary = [7].pack("N") + binary += "ssh-rsa" + ["e", "n"].each do |m| + val = public_key.send(m) + data = val.to_s(2) + + first_byte = data[0,1].unpack("c").first + if val < 0 + data[0] = [0x80 & first_byte].pack("c") + elsif first_byte < 0 + data = 0.chr + data + end + + binary += [data.length].pack("N") + data + end + + openssh_key = "ssh-rsa #{Base64.encode64(binary).gsub("\n", "")} vagrant" + public_key = public_key.to_pem + return [public_key, private_key, openssh_key] + end + end + end +end diff --git a/lib/vagrant/util/platform.rb b/lib/vagrant/util/platform.rb index d2b399616..706445c3b 100644 --- a/lib/vagrant/util/platform.rb +++ b/lib/vagrant/util/platform.rb @@ -144,11 +144,18 @@ module Vagrant path end + # Converts a given path to UNC format by adding a prefix and converting slashes. + # @param [String] path Path to convert to UNC for Windows + # @return [String] + def windows_unc_path(path) + "\\\\?\\" + path.gsub("/", "\\") + end + # Returns a boolean noting whether the terminal supports color. # output. def terminal_supports_colors? if windows? - return true if ENV.has_key?("ANSICON") + return true if ENV.key?("ANSICON") return true if cygwin? return true if ENV["TERM"] == "cygwin" return false diff --git a/lib/vagrant/util/ssh.rb b/lib/vagrant/util/ssh.rb index 44e0403aa..20afcdb04 100644 --- a/lib/vagrant/util/ssh.rb +++ b/lib/vagrant/util/ssh.rb @@ -83,7 +83,7 @@ module Vagrant # underneath the covers. In this case, we tell the user. if Platform.windows? r = Subprocess.execute(ssh_path) - if r.stdout.include?("PuTTY Link") + if r.stdout.include?("PuTTY Link") || r.stdout.include?("Plink: command-line connection utility") raise Errors::SSHIsPuttyLink, host: ssh_info[:host], port: ssh_info[:port], @@ -102,12 +102,14 @@ module Vagrant options[:username] = ssh_info[:username] options[:private_key_path] = ssh_info[:private_key_path] + log_level = ssh_info[:log_level] || "FATAL" + # Command line options command_options = [ "-p", options[:port].to_s, "-o", "Compression=yes", "-o", "DSAAuthentication=yes", - "-o", "LogLevel=FATAL", + "-o", "LogLevel=#{log_level}", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null"] @@ -156,17 +158,19 @@ module Vagrant # we really don't care since both work. ENV["nodosfilewarning"] = "1" if Platform.cygwin? + ssh = ssh_info[:ssh_command] || 'ssh' + # Invoke SSH with all our options if !opts[:subprocess] - LOGGER.info("Invoking SSH: #{command_options.inspect}") - SafeExec.exec("ssh", *command_options) + LOGGER.info("Invoking SSH: #{ssh} #{command_options.inspect}") + SafeExec.exec(ssh, *command_options) return end # If we're still here, it means we're supposed to subprocess # out to ssh rather than exec it. - LOGGER.info("Executing SSH in subprocess: #{command_options.inspect}") - process = ChildProcess.build("ssh", *command_options) + LOGGER.info("Executing SSH in subprocess: #{ssh} #{command_options.inspect}") + process = ChildProcess.build(ssh, *command_options) process.io.inherit! process.start process.wait diff --git a/lib/vagrant/util/subprocess.rb b/lib/vagrant/util/subprocess.rb index 92669d45a..d69286b33 100644 --- a/lib/vagrant/util/subprocess.rb +++ b/lib/vagrant/util/subprocess.rb @@ -25,6 +25,7 @@ module Vagrant def initialize(*command) @options = command.last.is_a?(Hash) ? command.pop : {} @command = command.dup + @command = @command.map { |s| s.encode(Encoding.default_external) } @command[0] = Which.which(@command[0]) if !File.file?(@command[0]) if !@command[0] raise Errors::CommandUnavailableWindows, file: command[0] if Platform.windows? @@ -71,23 +72,41 @@ module Vagrant process.io.stderr = stderr_writer process.duplex = true - # If we're in an installer on Mac and we're executing a command - # in the installer context, then force DYLD_LIBRARY_PATH to look - # at our libs first. - if Vagrant.in_installer? && Platform.darwin? + # Special installer-related things + if Vagrant.in_installer? installer_dir = ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"].to_s.downcase - if @command[0].downcase.include?(installer_dir) - @logger.info("Command in the installer. Specifying DYLD_LIBRARY_PATH...") - process.environment["DYLD_LIBRARY_PATH"] = - "#{installer_dir}/lib:#{ENV["DYLD_LIBRARY_PATH"]}" - else - @logger.debug("Command not in installer, not touching env vars.") + + # If we're in an installer on Mac and we're executing a command + # in the installer context, then force DYLD_LIBRARY_PATH to look + # at our libs first. + if Platform.darwin? + if @command[0].downcase.include?(installer_dir) + @logger.info("Command in the installer. Specifying DYLD_LIBRARY_PATH...") + process.environment["DYLD_LIBRARY_PATH"] = + "#{installer_dir}/lib:#{ENV["DYLD_LIBRARY_PATH"]}" + else + @logger.debug("Command not in installer, not touching env vars.") + end + + if File.setuid?(@command[0]) || File.setgid?(@command[0]) + @logger.info("Command is setuid/setgid, clearing DYLD_LIBRARY_PATH") + process.environment["DYLD_LIBRARY_PATH"] = "" + end end - if File.setuid?(@command[0]) || File.setgid?(@command[0]) - @logger.info("Command is setuid/setgid, clearing DYLD_LIBRARY_PATH") - process.environment["DYLD_LIBRARY_PATH"] = "" + # If the command that is being run is not inside the installer, reset + # the original environment - this is required for shelling out to + # other subprocesses that depend on environment variables (like Ruby + # and $GEM_PATH for example) + internal = [installer_dir, Vagrant.user_data_path.to_s.downcase]. + any? { |path| @command[0].downcase.include?(path) } + if !internal + @logger.info("Command not in installer, restoring original environment...") + jailbreak(process.environment) end + else + @logger.info("Vagrant not running in installer, restoring original environment...") + jailbreak(process.environment) end # Set the environment on the process if we must @@ -238,6 +257,58 @@ module Vagrant @stderr = stderr end end + + private + + # This is, quite possibly, the saddest function in all of Vagrant. + # + # If a user is running Vagrant via Bundler (but not via the official + # installer), we want to reset to the "original" environment so that when + # shelling out to other Ruby processes (specifically), the original + # environment is restored. This is super important for things like + # rbenv and chruby, who rely on environment variables to locate gems, but + # Bundler stomps on those environment variables like an angry T-Rex after + # watching Jurassic Park 2 and realizing they replaced you with CGI. + # + # If a user is running in Vagrant via the official installer, BUT trying + # to execute a subprocess *outside* of the installer, we want to reset to + # the "original" environment. In this case, the Vagrant installer actually + # knows what the original environment was and replaces it completely. + # + # Finally, we reset any Bundler-specific environment variables, since the + # subprocess being called could, itself, be Bundler. And Bundler does not + # behave very nicely in these circumstances. + # + # This function was added in Vagrant 1.7.3, but there is a failsafe + # because the author doesn't trust himself that this functionality won't + # break existing assumptions, so users can specify + # `VAGRANT_SKIP_SUBPROCESS_JAILBREAK` and none of the above will happen. + # + # This function modifies the given hash in place! + # + # @return [nil] + def jailbreak(env = {}) + return if ENV.key?("VAGRANT_SKIP_SUBPROCESS_JAILBREAK") + + env.replace(::Bundler::ORIGINAL_ENV) if defined?(::Bundler::ORIGINAL_ENV) + env.merge!(Vagrant.original_env) + + # Bundler does this, so I guess we should as well, since I think it + # other subprocesses that use Bundler will reload it + env["MANPATH"] = ENV["BUNDLE_ORIG_MANPATH"] + + # Replace all current environment BUNDLE_ variables to nil + ENV.each do |k,_| + env[k] = nil if k[0,7] == "BUNDLE_" + end + + # If RUBYOPT was set, unset it with Bundler + if ENV.key?("RUBYOPT") + env["RUBYOPT"] = ENV["RUBYOPT"].sub("-rbundler/setup", "") + end + + nil + end end end end diff --git a/lib/vagrant/vagrantfile.rb b/lib/vagrant/vagrantfile.rb index 511d1443e..16a7bc491 100644 --- a/lib/vagrant/vagrantfile.rb +++ b/lib/vagrant/vagrantfile.rb @@ -70,6 +70,10 @@ module Vagrant # Get the provider configuration from the final loaded configuration provider_config = config.vm.get_provider_config(provider) + # Create machine data directory if it doesn't exist + # XXX: Permissions error here. + FileUtils.mkdir_p(data_path) + # Create the machine and cache it for future calls. This will also # return the machine from this method. return Machine.new(name, provider, provider_cls, provider_config, @@ -114,24 +118,30 @@ module Vagrant name: name, provider: provider end - provider_plugin = Vagrant.plugin("2").manager.providers[provider] - if !provider_plugin - raise Errors::ProviderNotFound, - machine: name, provider: provider - end + provider_plugin = nil + provider_cls = nil + provider_options = {} + box_formats = nil + if provider != nil + provider_plugin = Vagrant.plugin("2").manager.providers[provider] + if !provider_plugin + raise Errors::ProviderNotFound, + machine: name, provider: provider + end - provider_cls = provider_plugin[0] - provider_options = provider_plugin[1] - box_formats = provider_options[:box_format] || provider + provider_cls = provider_plugin[0] + provider_options = provider_plugin[1] + box_formats = provider_options[:box_format] || provider - # Test if the provider is usable or not - begin - provider_cls.usable?(true) - rescue Errors::VagrantError => e - raise Errors::ProviderNotUsable, - machine: name.to_s, - provider: provider.to_s, - message: e.to_s + # Test if the provider is usable or not + begin + provider_cls.usable?(true) + rescue Errors::VagrantError => e + raise Errors::ProviderNotUsable, + machine: name.to_s, + provider: provider.to_s, + message: e.to_s + end end # Add the sub-machine configuration to the loader and keys @@ -153,7 +163,7 @@ module Vagrant local_keys = keys.dup # Load the box Vagrantfile, if there is one - if config.vm.box + if config.vm.box && boxes box = boxes.find(config.vm.box, box_formats, config.vm.box_version) if box box_vagrantfile = find_vagrantfile(box.directory) diff --git a/plugins/commands/box/command/add.rb b/plugins/commands/box/command/add.rb index 0356ee298..114601024 100644 --- a/plugins/commands/box/command/add.rb +++ b/plugins/commands/box/command/add.rb @@ -38,6 +38,10 @@ module VagrantPlugins options[:client_cert] = c end + o.on("--location-trusted", "Trust 'Location' header from HTTP redirects and use the same credentials for subsequent urls as for the initial one") do |l| + options[:location_trusted] = l + end + o.on("--provider PROVIDER", String, "Provider the box should satisfy") do |p| options[:provider] = p end @@ -47,7 +51,7 @@ module VagrantPlugins end o.separator "" - o.separator "The box descriptor can be the name of a box on Vagrant Cloud," + o.separator "The box descriptor can be the name of a box on HashiCorp's Atlas," o.separator "or a URL, or a local .box file, or a local .json file containing" o.separator "the catalog metadata." o.separator "" @@ -95,6 +99,7 @@ module VagrantPlugins box_download_ca_path: options[:ca_path], box_download_client_cert: options[:client_cert], box_download_insecure: options[:insecure], + box_download_location_trusted: options[:location_trusted], ui: Vagrant::UI::Prefixed.new(@env.ui, "box"), }) diff --git a/plugins/commands/destroy/command.rb b/plugins/commands/destroy/command.rb index e7a2f603f..141bfd38b 100644 --- a/plugins/commands/destroy/command.rb +++ b/plugins/commands/destroy/command.rb @@ -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 diff --git a/plugins/commands/login/client.rb b/plugins/commands/login/client.rb new file mode 100644 index 000000000..63b01f335 --- /dev/null +++ b/plugins/commands/login/client.rb @@ -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 diff --git a/plugins/commands/login/command.rb b/plugins/commands/login/command.rb new file mode 100644 index 000000000..203b09009 --- /dev/null +++ b/plugins/commands/login/command.rb @@ -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 diff --git a/plugins/commands/login/errors.rb b/plugins/commands/login/errors.rb new file mode 100644 index 000000000..614c37cf6 --- /dev/null +++ b/plugins/commands/login/errors.rb @@ -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 diff --git a/plugins/commands/login/locales/en.yml b/plugins/commands/login/locales/en.yml new file mode 100644 index 000000000..dba4d52d7 --- /dev/null +++ b/plugins/commands/login/locales/en.yml @@ -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. diff --git a/plugins/commands/login/middleware/add_authentication.rb b/plugins/commands/login/middleware/add_authentication.rb new file mode 100644 index 000000000..faaa3cfe7 --- /dev/null +++ b/plugins/commands/login/middleware/add_authentication.rb @@ -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 diff --git a/plugins/commands/login/plugin.rb b/plugins/commands/login/plugin.rb new file mode 100644 index 000000000..efb84a556 --- /dev/null +++ b/plugins/commands/login/plugin.rb @@ -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 diff --git a/plugins/commands/plugin/action/list_plugins.rb b/plugins/commands/plugin/action/list_plugins.rb index b7620ab84..37b168867 100644 --- a/plugins/commands/plugin/action/list_plugins.rb +++ b/plugins/commands/plugin/action/list_plugins.rb @@ -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 diff --git a/plugins/commands/plugin/action/plugin_exists_check.rb b/plugins/commands/plugin/action/plugin_exists_check.rb index 7a369976e..b7b7b9623 100644 --- a/plugins/commands/plugin/action/plugin_exists_check.rb +++ b/plugins/commands/plugin/action/plugin_exists_check.rb @@ -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 diff --git a/plugins/commands/push/command.rb b/plugins/commands/push/command.rb new file mode 100644 index 000000000..902a0c946 --- /dev/null +++ b/plugins/commands/push/command.rb @@ -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] 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 diff --git a/plugins/commands/push/plugin.rb b/plugins/commands/push/plugin.rb new file mode 100644 index 000000000..ecd24dd7a --- /dev/null +++ b/plugins/commands/push/plugin.rb @@ -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 diff --git a/plugins/commands/rdp/command.rb b/plugins/commands/rdp/command.rb index 156312dd2..587b99c33 100644 --- a/plugins/commands/rdp/command.rb +++ b/plugins/commands/rdp/command.rb @@ -70,8 +70,17 @@ module VagrantPlugins rdp_info[:username] = username end + if !rdp_info[:password] + password = ssh_info[:password] + if machine.config.vm.communicator == :winrm + password = machine.config.winrm.password + end + rdp_info[:password] = password + end + rdp_info[:host] ||= ssh_info[:host] rdp_info[:port] ||= machine.config.rdp.port + rdp_info[:username] ||= machine.config.rdp.username if rdp_info[:host] == "127.0.0.1" # We need to find a forwarded port... diff --git a/plugins/commands/rdp/config.rb b/plugins/commands/rdp/config.rb index 1384c6713..720463bd4 100644 --- a/plugins/commands/rdp/config.rb +++ b/plugins/commands/rdp/config.rb @@ -3,15 +3,18 @@ module VagrantPlugins class Config < Vagrant.plugin("2", :config) attr_accessor :port attr_accessor :search_port + attr_accessor :username def initialize @port = UNSET_VALUE @search_port = UNSET_VALUE + @username = UNSET_VALUE end def finalize! @port = 3389 if @port == UNSET_VALUE @search_port = 3389 if @search_port == UNSET_VALUE + @username = nil if @username == UNSET_VALUE end def validate(machine) diff --git a/plugins/commands/ssh_config/command.rb b/plugins/commands/ssh_config/command.rb index e534b7182..6013a90b7 100644 --- a/plugins/commands/ssh_config/command.rb +++ b/plugins/commands/ssh_config/command.rb @@ -40,7 +40,8 @@ module VagrantPlugins private_key_path: ssh_info[:private_key_path], forward_agent: ssh_info[:forward_agent], forward_x11: ssh_info[:forward_x11], - proxy_command: ssh_info[:proxy_command] + proxy_command: ssh_info[:proxy_command], + ssh_command: ssh_info[:ssh_command] } # Render the template and output directly to STDOUT diff --git a/plugins/commands/up/command.rb b/plugins/commands/up/command.rb index 7fdb6eb3c..826bea0ae 100644 --- a/plugins/commands/up/command.rb +++ b/plugins/commands/up/command.rb @@ -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 diff --git a/plugins/commands/up/start_mixins.rb b/plugins/commands/up/start_mixins.rb index e0aeb6de2..548d889ca 100644 --- a/plugins/commands/up/start_mixins.rb +++ b/plugins/commands/up/start_mixins.rb @@ -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 diff --git a/plugins/communicators/ssh/communicator.rb b/plugins/communicators/ssh/communicator.rb index f4d152002..8297329f9 100644 --- a/plugins/communicators/ssh/communicator.rb +++ b/plugins/communicators/ssh/communicator.rb @@ -11,9 +11,9 @@ require 'net/scp' require 'vagrant/util/ansi_escape_code_remover' require 'vagrant/util/file_mode' +require 'vagrant/util/keypair' require 'vagrant/util/platform' require 'vagrant/util/retryable' -require 'vagrant/util/ssh' module VagrantPlugins module CommunicatorSSH @@ -85,6 +85,12 @@ module VagrantPlugins raise rescue Vagrant::Errors::SSHKeyTypeNotSupported raise + rescue Vagrant::Errors::SSHKeyBadOwner + raise + rescue Vagrant::Errors::SSHKeyBadPermissions + raise + rescue Vagrant::Errors::SSHInsertKeyUnsupported + raise rescue Vagrant::Errors::VagrantError => e # Ignore it, SSH is not ready, some other error. end @@ -139,20 +145,41 @@ module VagrantPlugins # If we used a password, then insert the insecure key ssh_info = @machine.ssh_info - if ssh_info[:password] && ssh_info[:private_key_path].empty? - @logger.info("Inserting insecure key to avoid password") - @machine.ui.info(I18n.t("vagrant.inserting_insecure_key")) - @machine.guest.capability( - :insert_public_key, - Vagrant.source_root.join("keys", "vagrant.pub").read.chomp) + insert = ssh_info[:password] && ssh_info[:private_key_path].empty? + ssh_info[:private_key_path].each do |pk| + if insecure_key?(pk) + insert = true + @machine.ui.detail("\n"+I18n.t("vagrant.inserting_insecure_detected")) + break + end + end + + if insert + # If we don't have the power to insert/remove keys, then its an error + cap = @machine.guest.capability?(:insert_public_key) && + @machine.guest.capability?(:remove_public_key) + raise Vagrant::Errors::SSHInsertKeyUnsupported if !cap + + _pub, priv, openssh = Vagrant::Util::Keypair.create + + @logger.info("Inserting key to avoid password: #{openssh}") + @machine.ui.detail("\n"+I18n.t("vagrant.inserting_random_key")) + @machine.guest.capability(:insert_public_key, openssh) # Write out the private key in the data dir so that the # machine automatically picks it up. @machine.data_dir.join("private_key").open("w+") do |f| - f.write(Vagrant.source_root.join("keys", "vagrant").read) + f.write(priv) end - @machine.ui.info(I18n.t("vagrant.inserted_key")) + # Remove the old key if it exists + @machine.ui.detail(I18n.t("vagrant.inserting_remove_key")) + @machine.guest.capability( + :remove_public_key, + Vagrant.source_root.join("keys", "vagrant.pub").read.chomp) + + # Done, restart. + @machine.ui.detail(I18n.t("vagrant.inserted_key")) @connection.close @connection = nil @@ -265,8 +292,13 @@ module VagrantPlugins # There is a chance that the socket is closed despite us checking # 'closed?' above. To test this we need to send data through the # socket. + # + # We wrap the check itself in a 5 second timeout because there + # are some cases where this will just hang. begin - @connection.exec!("") + Timeout.timeout(5) do + @connection.exec!("") + end rescue Exception => e @logger.info("Connection errored, not re-using. Will reconnect.") @logger.debug(e.inspect) @@ -288,11 +320,17 @@ module VagrantPlugins raise Vagrant::Errors::SSHNotReady if ssh_info.nil? # Default some options - opts[:retries] = 5 if !opts.has_key?(:retries) + opts[:retries] = 5 if !opts.key?(:retries) + + # Set some valid auth methods. We disable the auth methods that + # we're not using if we don't have the right auth info. + auth_methods = ["none", "hostbased"] + auth_methods << "publickey" if ssh_info[:private_key_path] + auth_methods << "password" if ssh_info[:password] # Build the options we'll use to initiate the connection via Net::SSH common_connect_opts = { - auth_methods: ["none", "publickey", "hostbased", "password"], + auth_methods: auth_methods, config: false, forward_agent: ssh_info[:forward_agent], keys: ssh_info[:private_key_path], @@ -305,11 +343,6 @@ module VagrantPlugins verbose: :debug, } - # Check that the private key permissions are valid - ssh_info[:private_key_path].each do |path| - Vagrant::Util::SSH.check_key_permissions(Pathname.new(path)) - end - # Connect to SSH, giving it a few tries connection = nil begin @@ -407,6 +440,21 @@ module VagrantPlugins return yield connection if block_given? end + # The shell wrapper command used in shell_execute defined by + # the sudo and shell options. + def shell_cmd(opts) + sudo = opts[:sudo] + shell = opts[:shell] + + # Determine the shell to execute. Prefer the explicitly passed in shell + # over the default configured shell. If we are using `sudo` then we + # need to wrap the shell in a `sudo` call. + cmd = @machine.config.ssh.shell + cmd = shell if shell + cmd = @machine.config.ssh.sudo_command.gsub("%c", cmd) if sudo + cmd + end + # Executes the command on an SSH connection within a login shell. def shell_execute(connection, command, **opts) opts = { @@ -415,18 +463,10 @@ module VagrantPlugins }.merge(opts) sudo = opts[:sudo] - shell = opts[:shell] @logger.info("Execute: #{command} (sudo=#{sudo.inspect})") exit_status = nil - # Determine the shell to execute. Prefer the explicitly passed in shell - # over the default configured shell. If we are using `sudo` then we - # need to wrap the shell in a `sudo` call. - shell_cmd = @machine.config.ssh.shell - shell_cmd = shell if shell - shell_cmd = "sudo -E -H #{shell_cmd}" if sudo - # These variables are used to scrub PTY output if we're in a PTY pty = false pty_stdout = "" @@ -445,7 +485,7 @@ module VagrantPlugins end end - ch.exec(shell_cmd) do |ch2, _| + ch.exec(shell_cmd(opts)) do |ch2, _| # Setup the channel callbacks so we can get data and exit status ch2.on_data do |ch3, data| # Filter out the clear screen command @@ -594,6 +634,16 @@ module VagrantPlugins # Otherwise, just raise the error up raise end + + # This will test whether path is the Vagrant insecure private key. + # + # @param [String] path + def insecure_key?(path) + return false if !path + return false if !File.file?(path) + source_path = Vagrant.source_root.join("keys", "vagrant") + return File.read(path).chomp == source_path.read.chomp + end end end end diff --git a/plugins/communicators/winrm/command_filters/mkdir.rb b/plugins/communicators/winrm/command_filters/mkdir.rb old mode 100755 new mode 100644 diff --git a/plugins/communicators/winrm/command_filters/rm.rb b/plugins/communicators/winrm/command_filters/rm.rb index 251e26076..8ef79f148 100644 --- a/plugins/communicators/winrm/command_filters/rm.rb +++ b/plugins/communicators/winrm/command_filters/rm.rb @@ -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?('-') diff --git a/plugins/communicators/winrm/communicator.rb b/plugins/communicators/winrm/communicator.rb index 7609f16b3..fdb3fe615 100644 --- a/plugins/communicators/winrm/communicator.rb +++ b/plugins/communicators/winrm/communicator.rb @@ -25,22 +25,89 @@ module VagrantPlugins @logger.info("Initializing WinRMCommunicator") end + def wait_for_ready(timeout) + Timeout.timeout(timeout) do + # Wait for winrm_info to be ready + winrm_info = nil + while true + winrm_info = Helper.winrm_info(@machine) + break if winrm_info + sleep 0.5 + end + + # Got it! Let the user know what we're connecting to. + @machine.ui.detail("WinRM address: #{shell.host}:#{shell.port}") + @machine.ui.detail("WinRM username: #{shell.username}") + @machine.ui.detail("WinRM transport: #{shell.config.transport}") + + last_message = nil + last_message_repeat_at = 0 + while true + message = nil + begin + begin + return true if ready? + rescue Vagrant::Errors::VagrantError => e + @logger.info("WinRM not ready: #{e.inspect}") + raise + end + rescue Errors::ConnectionTimeout + message = "Connection timeout." + rescue Errors::AuthenticationFailed + message = "Authentication failure." + rescue Errors::Disconnected + message = "Remote connection disconnect." + rescue Errors::ConnectionRefused + message = "Connection refused." + rescue Errors::ConnectionReset + message = "Connection reset." + rescue Errors::HostDown + message = "Host appears down." + rescue Errors::NoRoute + message = "Host unreachable." + rescue Errors::TransientError => e + # Any other retriable errors + message = e.message + end + + # If we have a message to show, then show it. We don't show + # repeated messages unless they've been repeating longer than + # 10 seconds. + if message + message_at = Time.now.to_f + show_message = true + if last_message == message + show_message = (message_at - last_message_repeat_at) > 10.0 + end + + if show_message + @machine.ui.detail("Warning: #{message} Retrying...") + last_message = message + last_message_repeat_at = message_at + end + end + end + end + rescue Timeout::Error + return false + end + def ready? @logger.info("Checking whether WinRM is ready...") - Timeout.timeout(@machine.config.winrm.timeout) do + result = Timeout.timeout(@machine.config.winrm.timeout) do shell(true).powershell("hostname") end @logger.info("WinRM is ready!") return true - rescue Vagrant::Errors::VagrantError => e - # We catch a `VagrantError` which would signal that something went - # wrong expectedly in the `connect`, which means we didn't connect. + rescue Errors::TransientError => e + # We catch a `TransientError` which would signal that something went + # that might work if we wait and retry. @logger.info("WinRM not up: #{e.inspect}") # We reset the shell to trigger calling of winrm_finder again. - # This resolves a problem when using vSphere where the ssh_info was not refreshing + # This resolves a problem when using vSphere where the winrm_info was not refreshing # thus never getting the correct hostname. @shell = nil return false @@ -67,24 +134,28 @@ module VagrantPlugins }.merge(opts || {}) opts[:good_exit] = Array(opts[:good_exit]) - - if opts[:elevated] - guest_script_path = create_elevated_shell_script(command) - command = "powershell -executionpolicy bypass -file #{guest_script_path}" - end - + command = wrap_in_scheduled_task(command) if opts[:elevated] output = shell.send(opts[:shell], command, &block) execution_output(output, opts) end alias_method :sudo, :execute def test(command, opts=nil) - # If this is a *nix command with no Windows equivilant, assume failure + # If this is a *nix command (which we know about) with no Windows + # equivilant, assume failure command = @cmd_filter.filter(command) return false if command.empty? - opts = { error_check: false }.merge(opts || {}) - execute(command, opts) == 0 + opts = { + command: command, + elevated: false, + error_check: false, + }.merge(opts || {}) + + # If we're passed a *nix command which PS can't parse we get exit code + # 0, but output in stderr. We need to check both exit code and stderr. + output = shell.send(:powershell, command) + return output[:exitcode] == 0 && flatten_stderr(output).length == 0 end def upload(from, to) @@ -106,27 +177,20 @@ module VagrantPlugins WinRMShell.new( winrm_info[:host], - @machine.config.winrm.username, - @machine.config.winrm.password, - port: winrm_info[:port], - timeout_in_seconds: @machine.config.winrm.timeout, - max_tries: @machine.config.winrm.max_tries, + winrm_info[:port], + @machine.config.winrm ) end - # Creates and uploads a PowerShell script which wraps the specified - # command in a scheduled task. The scheduled task allows commands to - # run on the guest as a true local admin without any of the restrictions - # that WinRM puts in place. + # Creates and uploads a PowerShell script which wraps a command in a + # scheduled task. The scheduled task allows commands to run on the guest + # as a true local admin without any of the restrictions that WinRM puts + # in place. # - # @return The path to elevated_shell.ps1 on the guest - def create_elevated_shell_script(command) + # @return The wrapper command to execute + def wrap_in_scheduled_task(command) path = File.expand_path("../scripts/elevated_shell.ps1", __FILE__) - script = Vagrant::Util::TemplateRenderer.render(path, options: { - username: shell.username, - password: shell.password, - command: command.gsub("\"", "`\""), - }) + script = Vagrant::Util::TemplateRenderer.render(path) guest_script_path = "c:/tmp/vagrant-elevated-shell.ps1" file = Tempfile.new(["vagrant-elevated-shell", "ps1"]) begin @@ -138,7 +202,15 @@ module VagrantPlugins file.close file.unlink end - guest_script_path + + # convert to double byte unicode string then base64 encode + # just like PowerShell -EncodedCommand expects + wrapped_encoded_command = Base64.strict_encode64( + "#{command}; exit $LASTEXITCODE".encode('UTF-16LE', 'UTF-8')) + + "powershell -executionpolicy bypass -file \"#{guest_script_path}\" " + + "-username \"#{shell.username}\" -password \"#{shell.password}\" " + + "-encoded_command \"#{wrapped_encoded_command}\"" end # Handles the raw WinRM shell result and converts it to a @@ -156,8 +228,8 @@ module VagrantPlugins def raise_execution_error(output, opts) # WinRM can return multiple stderr and stdout entries error_opts = opts.merge( - stdout: output[:data].collect { |e| e[:stdout] }.join, - stderr: output[:data].collect { |e| e[:stderr] }.join + stdout: flatten_stdout(output), + stderr: flatten_stderr(output) ) # Use a different error message key if the caller gave us one, @@ -167,6 +239,20 @@ module VagrantPlugins # Raise the error, use the type the caller gave us or the comm default raise opts[:error_class], error_opts end + + + # TODO: Replace with WinRM Output class when WinRM 1.3 is released + def flatten_stderr(output) + output[:data].map do | line | + line[:stderr] + end.compact.join + end + + def flatten_stdout(output) + output[:data].map do | line | + line[:flatten_stdout] + end.compact.join + end end #WinRM class end end diff --git a/plugins/communicators/winrm/config.rb b/plugins/communicators/winrm/config.rb index 155ac0567..79901d503 100644 --- a/plugins/communicators/winrm/config.rb +++ b/plugins/communicators/winrm/config.rb @@ -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 diff --git a/plugins/communicators/winrm/errors.rb b/plugins/communicators/winrm/errors.rb index 552b8a300..6a5df1be1 100644 --- a/plugins/communicators/winrm/errors.rb +++ b/plugins/communicators/winrm/errors.rb @@ -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 diff --git a/plugins/communicators/winrm/file_manager.rb b/plugins/communicators/winrm/file_manager.rb deleted file mode 100644 index 71586b811..000000000 --- a/plugins/communicators/winrm/file_manager.rb +++ /dev/null @@ -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 diff --git a/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb b/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb index 454e5e874..17767e436 100644 --- a/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb +++ b/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb @@ -1,6 +1,4 @@ -$command = "<%= options[:command] %>" + '; exit $LASTEXITCODE' -$user = '<%= options[:username] %>' -$password = '<%= options[:password] %>' +param([String]$username, [String]$password, [String]$encoded_command) $task_name = "WinRM_Elevated_Shell" $out_file = "$env:SystemRoot\Temp\WinRM_Elevated_Shell.log" @@ -14,7 +12,7 @@ $task_xml = @' - {user} + {username} Password HighestAvailable @@ -27,7 +25,7 @@ $task_xml = @' false false - true + false false true @@ -47,19 +45,17 @@ $task_xml = @' '@ -$bytes = [System.Text.Encoding]::Unicode.GetBytes($command) -$encoded_command = [Convert]::ToBase64String($bytes) $arguments = "/c powershell.exe -EncodedCommand $encoded_command > $out_file 2>&1" $task_xml = $task_xml.Replace("{arguments}", $arguments) -$task_xml = $task_xml.Replace("{user}", $user) +$task_xml = $task_xml.Replace("{username}", $username) $schedule = New-Object -ComObject "Schedule.Service" $schedule.Connect() $task = $schedule.NewTask($null) $task.XmlText = $task_xml $folder = $schedule.GetFolder("\") -$folder.RegisterTaskDefinition($task_name, $task, 6, $user, $password, 1, $null) | Out-Null +$folder.RegisterTaskDefinition($task_name, $task, 6, $username, $password, 1, $null) | Out-Null $registered_task = $folder.GetTask("\$task_name") $registered_task.Run($null) | Out-Null diff --git a/plugins/communicators/winrm/shell.rb b/plugins/communicators/winrm/shell.rb index 353c0e84d..d9d5f28ce 100644 --- a/plugins/communicators/winrm/shell.rb +++ b/plugins/communicators/winrm/shell.rb @@ -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 diff --git a/plugins/guests/arch/cap/configure_networks.rb b/plugins/guests/arch/cap/configure_networks.rb index 5d2bc2aa6..fd06c24ea 100644 --- a/plugins/guests/arch/cap/configure_networks.rb +++ b/plugins/guests/arch/cap/configure_networks.rb @@ -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 diff --git a/plugins/guests/atomic/cap/change_host_name.rb b/plugins/guests/atomic/cap/change_host_name.rb new file mode 100644 index 000000000..aecec88e2 --- /dev/null +++ b/plugins/guests/atomic/cap/change_host_name.rb @@ -0,0 +1,11 @@ +module VagrantPlugins + module GuestAtomic + module Cap + class ChangeHostName + def self.change_host_name(machine, name) + machine.communicate.sudo("hostnamectl set-hostname #{name}") + end + end + end + end +end diff --git a/plugins/guests/atomic/cap/docker.rb b/plugins/guests/atomic/cap/docker.rb new file mode 100644 index 000000000..55052c8a7 --- /dev/null +++ b/plugins/guests/atomic/cap/docker.rb @@ -0,0 +1,11 @@ +module VagrantPlugins + module GuestAtomic + module Cap + module Docker + def self.docker_daemon_running(machine) + machine.communicate.test("test -S /run/docker.sock") + end + end + end + end +end diff --git a/plugins/guests/atomic/guest.rb b/plugins/guests/atomic/guest.rb new file mode 100644 index 000000000..3fbb439db --- /dev/null +++ b/plugins/guests/atomic/guest.rb @@ -0,0 +1,11 @@ +require "vagrant" + +module VagrantPlugins + module GuestAtomic + class Guest < Vagrant.plugin("2", :guest) + def detect?(machine) + machine.communicate.test("grep 'ostree=' /proc/cmdline") + end + end + end +end diff --git a/plugins/guests/atomic/plugin.rb b/plugins/guests/atomic/plugin.rb new file mode 100644 index 000000000..a410ed2fc --- /dev/null +++ b/plugins/guests/atomic/plugin.rb @@ -0,0 +1,25 @@ +require 'vagrant' + +module VagrantPlugins + module GuestAtomic + class Plugin < Vagrant.plugin("2") + name "Atomic Host guest" + description "Atomic Host guest support." + + guest("atomic", "fedora") do + require File.expand_path("../guest", __FILE__) + Guest + end + + guest_capability("atomic", "change_host_name") do + require_relative "cap/change_host_name" + Cap::ChangeHostName + end + + guest_capability("atomic", "docker_daemon_running") do + require_relative "cap/docker" + Cap::Docker + end + end + end +end diff --git a/plugins/guests/darwin/cap/change_host_name.rb b/plugins/guests/darwin/cap/change_host_name.rb index 80df9870d..c65c32642 100644 --- a/plugins/guests/darwin/cap/change_host_name.rb +++ b/plugins/guests/darwin/cap/change_host_name.rb @@ -4,7 +4,11 @@ module VagrantPlugins class ChangeHostName def self.change_host_name(machine, name) if !machine.communicate.test("hostname -f | grep '^#{name}$' || hostname -s | grep '^#{name}$'") + machine.communicate.sudo("scutil --set ComputerName #{name}") machine.communicate.sudo("scutil --set HostName #{name}") + # LocalHostName shouldn't contain dots. + # It is used by Bonjour and visible through file sharing services. + machine.communicate.sudo("scutil --set LocalHostName #{name.gsub(/\.+/, '')}") machine.communicate.sudo("hostname #{name}") end end diff --git a/plugins/guests/darwin/cap/choose_addressable_ip_addr.rb b/plugins/guests/darwin/cap/choose_addressable_ip_addr.rb new file mode 100644 index 000000000..f5b578ebd --- /dev/null +++ b/plugins/guests/darwin/cap/choose_addressable_ip_addr.rb @@ -0,0 +1,20 @@ +module VagrantPlugins + module GuestDarwin + module Cap + module ChooseAddressableIPAddr + def self.choose_addressable_ip_addr(machine, possible) + machine.communicate.tap do |comm| + possible.each do |ip| + command = "ping -c1 -t1 #{ip}" + if comm.test(command) + return ip + end + end + end + + nil + end + end + end + end +end diff --git a/plugins/guests/darwin/cap/insert_public_key.rb b/plugins/guests/darwin/cap/insert_public_key.rb new file mode 100644 index 000000000..59e5dd58c --- /dev/null +++ b/plugins/guests/darwin/cap/insert_public_key.rb @@ -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 diff --git a/plugins/guests/darwin/cap/mount_smb_shared_folder.rb b/plugins/guests/darwin/cap/mount_smb_shared_folder.rb new file mode 100644 index 000000000..5f1cdaec4 --- /dev/null +++ b/plugins/guests/darwin/cap/mount_smb_shared_folder.rb @@ -0,0 +1,37 @@ +require "vagrant/util/retryable" +require "shellwords" + +module VagrantPlugins + module GuestDarwin + module Cap + class MountSMBSharedFolder + extend Vagrant::Util::Retryable + def self.mount_smb_shared_folder(machine, name, guestpath, options) + expanded_guest_path = machine.guest.capability(:shell_expand_guest_path, guestpath) + + mount_point_owner = options[:owner]; + if mount_point_owner + machine.communicate.sudo("mkdir -p #{expanded_guest_path}") + machine.communicate.sudo("chown #{mount_point_owner} #{expanded_guest_path}") + else + # fallback to assumption that user has permission + # to create the specified mountpoint + machine.communicate.execute("mkdir -p #{expanded_guest_path}") + end + + smb_password = Shellwords.shellescape(options[:smb_password]) + mount_options = options[:mount_options]; + mount_command = "mount -t smbfs " + + (mount_options ? "-o '#{mount_options.join(",")}' " : "") + + "'//#{options[:smb_username]}:#{smb_password}@#{options[:smb_host]}/#{name}' " + + "#{expanded_guest_path}" + retryable(on: Vagrant::Errors::DarwinMountFailed, tries: 10, sleep: 2) do + machine.communicate.execute( + mount_command, + error_class: Vagrant::Errors::DarwinMountFailed) + end + end + end + end + end +end diff --git a/plugins/guests/darwin/cap/remove_public_key.rb b/plugins/guests/darwin/cap/remove_public_key.rb new file mode 100644 index 000000000..1e0fe415a --- /dev/null +++ b/plugins/guests/darwin/cap/remove_public_key.rb @@ -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 diff --git a/plugins/guests/darwin/cap/rsync.rb b/plugins/guests/darwin/cap/rsync.rb index e98d6e5ae..7e5d6d9a5 100644 --- a/plugins/guests/darwin/cap/rsync.rb +++ b/plugins/guests/darwin/cap/rsync.rb @@ -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 diff --git a/plugins/guests/darwin/plugin.rb b/plugins/guests/darwin/plugin.rb index 9736500bd..8e6dc279b 100644 --- a/plugins/guests/darwin/plugin.rb +++ b/plugins/guests/darwin/plugin.rb @@ -16,6 +16,11 @@ module VagrantPlugins Cap::ChangeHostName end + guest_capability("darwin", "choose_addressable_ip_addr") do + require_relative "cap/choose_addressable_ip_addr" + Cap::ChooseAddressableIPAddr + end + guest_capability("darwin", "configure_networks") do require_relative "cap/configure_networks" Cap::ConfigureNetworks @@ -26,16 +31,31 @@ module VagrantPlugins Cap::Halt end + guest_capability("darwin", "insert_public_key") do + require_relative "cap/insert_public_key" + Cap::InsertPublicKey + end + guest_capability("darwin", "mount_nfs_folder") do require_relative "cap/mount_nfs_folder" Cap::MountNFSFolder end + guest_capability("darwin", "mount_smb_shared_folder") do + require_relative "cap/mount_smb_shared_folder" + Cap::MountSMBSharedFolder + end + guest_capability("darwin", "mount_vmware_shared_folder") do require_relative "cap/mount_vmware_shared_folder" Cap::MountVmwareSharedFolder end + guest_capability("darwin", "remove_public_key") do + require_relative "cap/remove_public_key" + Cap::RemovePublicKey + end + guest_capability("darwin", "rsync_installed") do require_relative "cap/rsync" Cap::RSync diff --git a/plugins/guests/debian/cap/configure_networks.rb b/plugins/guests/debian/cap/configure_networks.rb index 49da980bf..cf416ec09 100644 --- a/plugins/guests/debian/cap/configure_networks.rb +++ b/plugins/guests/debian/cap/configure_networks.rb @@ -13,9 +13,8 @@ module VagrantPlugins machine.communicate.tap do |comm| # First, remove any previous network modifications # from the interface file. - comm.sudo("sed -e '/^#VAGRANT-BEGIN/,/^#VAGRANT-END/ d' /etc/network/interfaces > /tmp/vagrant-network-interfaces") - comm.sudo("su -c 'cat /tmp/vagrant-network-interfaces > /etc/network/interfaces'") - comm.sudo("rm -f /tmp/vagrant-network-interfaces") + comm.sudo("sed -e '/^#VAGRANT-BEGIN/,$ d' /etc/network/interfaces > /tmp/vagrant-network-interfaces.pre") + comm.sudo("sed -ne '/^#VAGRANT-END/,$ p' /etc/network/interfaces | tac | sed -e '/^#VAGRANT-END/,$ d' | tac > /tmp/vagrant-network-interfaces.post") # Accumulate the configurations to add to the interfaces file as # well as what interfaces we're actually configuring since we use that @@ -47,8 +46,8 @@ module VagrantPlugins comm.sudo("/sbin/ip addr flush dev eth#{interface} 2> /dev/null") end - comm.sudo("cat /tmp/vagrant-network-entry >> /etc/network/interfaces") - comm.sudo("rm -f /tmp/vagrant-network-entry") + comm.sudo('cat /tmp/vagrant-network-interfaces.pre /tmp/vagrant-network-entry /tmp/vagrant-network-interfaces.post > /etc/network/interfaces') + comm.sudo('rm -f /tmp/vagrant-network-interfaces.pre /tmp/vagrant-network-entry /tmp/vagrant-network-interfaces.post') # Bring back up each network interface, reconfigured interfaces.each do |interface| diff --git a/plugins/guests/debian/guest.rb b/plugins/guests/debian/guest.rb index a7f3d65de..be08b3605 100644 --- a/plugins/guests/debian/guest.rb +++ b/plugins/guests/debian/guest.rb @@ -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 diff --git a/plugins/guests/debian8/cap/halt.rb b/plugins/guests/debian8/cap/halt.rb new file mode 100644 index 000000000..932281347 --- /dev/null +++ b/plugins/guests/debian8/cap/halt.rb @@ -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 diff --git a/plugins/guests/debian8/guest.rb b/plugins/guests/debian8/guest.rb new file mode 100644 index 000000000..ec95e9e85 --- /dev/null +++ b/plugins/guests/debian8/guest.rb @@ -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 diff --git a/plugins/guests/debian8/plugin.rb b/plugins/guests/debian8/plugin.rb new file mode 100644 index 000000000..1f566f5fa --- /dev/null +++ b/plugins/guests/debian8/plugin.rb @@ -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 diff --git a/plugins/guests/fedora/cap/configure_networks.rb b/plugins/guests/fedora/cap/configure_networks.rb index 549a99dff..0e2a0d057 100644 --- a/plugins/guests/fedora/cap/configure_networks.rb +++ b/plugins/guests/fedora/cap/configure_networks.rb @@ -16,17 +16,18 @@ module VagrantPlugins virtual = false interface_names = Array.new + interface_names_by_slot = Array.new machine.communicate.sudo("/usr/sbin/biosdevname; echo $?") do |_, result| - virtual = true if result.chomp == '4' + virtual = true if ['4', '127'].include? result.chomp end if virtual - machine.communicate.sudo("ls /sys/class/net | grep -v lo") do |_, result| + machine.communicate.sudo("ls /sys/class/net | egrep -v lo\\|docker") do |_, result| interface_names = result.split("\n") end - interface_names = networks.map do |network| - "eth#{network[:interface]}" + interface_names_by_slot = networks.map do |network| + "#{interface_names[network[:interface]]}" end else machine.communicate.sudo("/usr/sbin/biosdevname -d | grep Kernel | cut -f2 -d: | sed -e 's/ //;'") do |_, result| @@ -44,18 +45,39 @@ module VagrantPlugins "eth#{network[:interface]}" end + interface_names_by_slot = interface_names.dup interface_name_pairs.each do |interface_name, previous_interface_name| if setting_interface_names.index(previous_interface_name) == nil - interface_names.delete(interface_name) + interface_names_by_slot.delete(interface_name) end end end + # Read interface MAC addresses for later matching + mac_addresses = Array.new(interface_names.length) + interface_names.each_with_index do |ifname, index| + machine.communicate.sudo("cat /sys/class/net/#{ifname}/address") do |_, result| + mac_addresses[index] = result.strip + end + end + # Accumulate the configurations to add to the interfaces file as well # as what interfaces we're actually configuring since we use that later. interfaces = Set.new networks.each do |network| - interface = interface_names[network[:interface]-1] + interface = nil + if network[:mac_address] + found_idx = mac_addresses.find_index(network[:mac_address]) + # Ignore network if requested MAC address could not be found + next if found_idx.nil? + interface = interface_names[found_idx] + else + ifname_by_slot = interface_names_by_slot[network[:interface]-1] + # Don't overwrite if interface was already matched via MAC address + next if interfaces.include?(ifname_by_slot) + interface = ifname_by_slot + end + interfaces.add(interface) network[:device] = interface @@ -85,6 +107,7 @@ module VagrantPlugins interfaces.each do |interface| retryable(on: Vagrant::Errors::VagrantError, tries: 3, sleep: 2) do machine.communicate.sudo("cat /tmp/vagrant-network-entry_#{interface} >> #{network_scripts_dir}/ifcfg-#{interface}") + machine.communicate.sudo("which nmcli >/dev/null 2>&1 && nmcli c reload #{interface}") machine.communicate.sudo("/sbin/ifdown #{interface}", error_check: true) machine.communicate.sudo("/sbin/ifup #{interface}") end diff --git a/plugins/guests/fedora/cap/flavor.rb b/plugins/guests/fedora/cap/flavor.rb new file mode 100644 index 000000000..2a29f50b5 --- /dev/null +++ b/plugins/guests/fedora/cap/flavor.rb @@ -0,0 +1,23 @@ +module VagrantPlugins + module GuestFedora + module Cap + class Flavor + def self.flavor(machine) + # Read the version file + version = nil + machine.communicate.sudo("grep VERSION_ID /etc/os-release") do |type, data| + if type == :stdout + version = data.split("=")[1].chomp.to_i + end + end + + if version.nil? + return :fedora + else + return "fedora_#{version}".to_sym + end + end + end + end + end +end diff --git a/plugins/guests/fedora/cap/network_scripts_dir.rb b/plugins/guests/fedora/cap/network_scripts_dir.rb index 3ce0e43c3..82a4abad7 100644 --- a/plugins/guests/fedora/cap/network_scripts_dir.rb +++ b/plugins/guests/fedora/cap/network_scripts_dir.rb @@ -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" diff --git a/plugins/guests/fedora/guest.rb b/plugins/guests/fedora/guest.rb index 5c74e7869..c1b48d4ab 100644 --- a/plugins/guests/fedora/guest.rb +++ b/plugins/guests/fedora/guest.rb @@ -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 1[6789]\\|Fedora release 2[0-9]' /etc/redhat-release") end end end diff --git a/plugins/guests/fedora/plugin.rb b/plugins/guests/fedora/plugin.rb index 66b2d111f..118a2f13c 100644 --- a/plugins/guests/fedora/plugin.rb +++ b/plugins/guests/fedora/plugin.rb @@ -25,6 +25,11 @@ module VagrantPlugins require_relative "cap/network_scripts_dir" Cap::NetworkScriptsDir end + + guest_capability("fedora", "flavor") do + require_relative "cap/flavor" + Cap::Flavor + end end end end diff --git a/plugins/guests/freebsd/cap/mount_nfs_folder.rb b/plugins/guests/freebsd/cap/mount_nfs_folder.rb index c36f928ca..58d8f785c 100644 --- a/plugins/guests/freebsd/cap/mount_nfs_folder.rb +++ b/plugins/guests/freebsd/cap/mount_nfs_folder.rb @@ -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 diff --git a/plugins/guests/freebsd/cap/remove_public_key.rb b/plugins/guests/freebsd/cap/remove_public_key.rb new file mode 100644 index 000000000..8d5526ca4 --- /dev/null +++ b/plugins/guests/freebsd/cap/remove_public_key.rb @@ -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 .bak '/^.*#{contents}.*$/d' ~/.ssh/authorized_keys") + end + end + end + end + end + end +end diff --git a/plugins/guests/freebsd/cap/rsync.rb b/plugins/guests/freebsd/cap/rsync.rb index 2876b5943..c8fb36006 100644 --- a/plugins/guests/freebsd/cap/rsync.rb +++ b/plugins/guests/freebsd/cap/rsync.rb @@ -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 diff --git a/plugins/guests/freebsd/plugin.rb b/plugins/guests/freebsd/plugin.rb index 49aa3298b..a4ece8de2 100644 --- a/plugins/guests/freebsd/plugin.rb +++ b/plugins/guests/freebsd/plugin.rb @@ -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 diff --git a/plugins/guests/funtoo/cap/configure_networks.rb b/plugins/guests/funtoo/cap/configure_networks.rb index 466e3a57b..c52182b6f 100644 --- a/plugins/guests/funtoo/cap/configure_networks.rb +++ b/plugins/guests/funtoo/cap/configure_networks.rb @@ -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}") diff --git a/plugins/guests/linux/cap/mount_nfs.rb b/plugins/guests/linux/cap/mount_nfs.rb index 9fbb72d47..ecc3ea9ee 100644 --- a/plugins/guests/linux/cap/mount_nfs.rb +++ b/plugins/guests/linux/cap/mount_nfs.rb @@ -33,7 +33,7 @@ module VagrantPlugins end # Emit an upstart event if we can - if machine.communicate.test("test -x /sbin/initctl") + if machine.communicate.test("test -x /sbin/initctl && test 'upstart' = $(basename $(sudo readlink /proc/1/exe))") machine.communicate.sudo( "/sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT=#{expanded_guest_path}") end diff --git a/plugins/guests/linux/cap/mount_smb_shared_folder.rb b/plugins/guests/linux/cap/mount_smb_shared_folder.rb index d50aae686..136ccfb8a 100644 --- a/plugins/guests/linux/cap/mount_smb_shared_folder.rb +++ b/plugins/guests/linux/cap/mount_smb_shared_folder.rb @@ -27,10 +27,14 @@ module VagrantPlugins smb_password = Shellwords.shellescape(options[:smb_password]) + # If a domain is provided in the username, separate it + username, domain = (options[:smb_username] || '').split('@', 2) + options[:mount_options] ||= [] options[:mount_options] << "sec=ntlm" - options[:mount_options] << "username=#{options[:smb_username]}" - options[:mount_options] << "pass=#{smb_password}" + options[:mount_options] << "username=#{username}" + options[:mount_options] << "password=#{smb_password}" + options[:mount_options] << "domain=#{domain}" if domain # First mount command uses getent to get the group mount_options = "-o uid=#{mount_uid},gid=#{mount_gid}" @@ -51,10 +55,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,14 +78,15 @@ module VagrantPlugins command.gsub!(smb_password, "PASSWORDHIDDEN") raise Vagrant::Errors::LinuxMountFailed, - command: command + command: command, + output: stderr end sleep 2 end # Emit an upstart event if we can - if machine.communicate.test("test -x /sbin/initctl") + if machine.communicate.test("test -x /sbin/initctl && test 'upstart' = $(basename $(sudo readlink /proc/1/exe))") machine.communicate.sudo( "/sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT=#{expanded_guest_path}") end diff --git a/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb b/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb index 83c197d92..56c864b6b 100644 --- a/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb +++ b/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb @@ -80,7 +80,7 @@ module VagrantPlugins end # Emit an upstart event if we can - if machine.communicate.test("test -x /sbin/initctl") + if machine.communicate.test("test -x /sbin/initctl && test 'upstart' = $(basename $(sudo readlink /proc/1/exe))") machine.communicate.sudo( "/sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT=#{expanded_guest_path}") end diff --git a/plugins/guests/linux/cap/read_ip_address.rb b/plugins/guests/linux/cap/read_ip_address.rb index eca1aec54..5fa9fea15 100644 --- a/plugins/guests/linux/cap/read_ip_address.rb +++ b/plugins/guests/linux/cap/read_ip_address.rb @@ -5,7 +5,7 @@ module VagrantPlugins def self.read_ip_address(machine) command = "LANG=en ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f2 | awk '{ print $1 }'" result = "" - machine.communicate.execute(command) do |type, data| + machine.communicate.sudo(command) do |type, data| result << data if type == :stdout end diff --git a/plugins/guests/linux/cap/remove_public_key.rb b/plugins/guests/linux/cap/remove_public_key.rb new file mode 100644 index 000000000..a8d773a40 --- /dev/null +++ b/plugins/guests/linux/cap/remove_public_key.rb @@ -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 diff --git a/plugins/guests/linux/cap/rsync.rb b/plugins/guests/linux/cap/rsync.rb index 113b18dd0..424d20f46 100644 --- a/plugins/guests/linux/cap/rsync.rb +++ b/plugins/guests/linux/cap/rsync.rb @@ -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 diff --git a/plugins/guests/linux/cap/shell_expand_guest_path.rb b/plugins/guests/linux/cap/shell_expand_guest_path.rb index 341c0a1a8..cc32d7f7c 100644 --- a/plugins/guests/linux/cap/shell_expand_guest_path.rb +++ b/plugins/guests/linux/cap/shell_expand_guest_path.rb @@ -11,8 +11,10 @@ module VagrantPlugins end end - # The last line is the path we care about - real_path = real_path.split("\n").last.chomp + if real_path + # The last line is the path we care about + real_path = real_path.split("\n").last.chomp + end if !real_path # If no real guest path was detected, this is really strange diff --git a/plugins/guests/linux/plugin.rb b/plugins/guests/linux/plugin.rb index a2bb1167c..cadd4aa95 100644 --- a/plugins/guests/linux/plugin.rb +++ b/plugins/guests/linux/plugin.rb @@ -62,6 +62,11 @@ module VagrantPlugins Cap::ReadIPAddress end + guest_capability("linux", "remove_public_key") do + require_relative "cap/remove_public_key" + Cap::RemovePublicKey + end + guest_capability("linux", "rsync_installed") do require_relative "cap/rsync" Cap::RSync diff --git a/plugins/guests/netbsd/cap/remove_public_key.rb b/plugins/guests/netbsd/cap/remove_public_key.rb new file mode 100644 index 000000000..d25e97679 --- /dev/null +++ b/plugins/guests/netbsd/cap/remove_public_key.rb @@ -0,0 +1,21 @@ +require "vagrant/util/shell_quote" + +module VagrantPlugins + module GuestNetBSD + 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 diff --git a/plugins/guests/netbsd/plugin.rb b/plugins/guests/netbsd/plugin.rb index 490c73644..9636c6630 100644 --- a/plugins/guests/netbsd/plugin.rb +++ b/plugins/guests/netbsd/plugin.rb @@ -36,6 +36,11 @@ module VagrantPlugins Cap::MountNFSFolder end + guest_capability("netbsd", "remove_public_key") do + require_relative "cap/remove_public_key" + Cap::RemovePublicKey + end + guest_capability("netbsd", "rsync_install") do require_relative "cap/rsync" Cap::RSync diff --git a/plugins/guests/openbsd/cap/insert_public_key.rb b/plugins/guests/openbsd/cap/insert_public_key.rb index ca837e23e..42437283c 100644 --- a/plugins/guests/openbsd/cap/insert_public_key.rb +++ b/plugins/guests/openbsd/cap/insert_public_key.rb @@ -11,7 +11,7 @@ module VagrantPlugins machine.communicate.tap do |comm| comm.execute("mkdir -p ~/.ssh") comm.execute("chmod 0700 ~/.ssh") - comm.execute("printf '#{contents}' >> ~/.ssh/authorized_keys") + comm.execute("printf '#{contents}\\n' >> ~/.ssh/authorized_keys") comm.execute("chmod 0600 ~/.ssh/authorized_keys") end end diff --git a/plugins/guests/openbsd/cap/remove_public_key.rb b/plugins/guests/openbsd/cap/remove_public_key.rb new file mode 100644 index 000000000..94cfce03b --- /dev/null +++ b/plugins/guests/openbsd/cap/remove_public_key.rb @@ -0,0 +1,21 @@ +require "vagrant/util/shell_quote" + +module VagrantPlugins + module GuestOpenBSD + 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 diff --git a/plugins/guests/openbsd/plugin.rb b/plugins/guests/openbsd/plugin.rb index 7a64b16d6..a1ddfe9c1 100644 --- a/plugins/guests/openbsd/plugin.rb +++ b/plugins/guests/openbsd/plugin.rb @@ -36,6 +36,11 @@ module VagrantPlugins Cap::MountNFSFolder end + guest_capability("openbsd", "remove_public_key") do + require_relative "cap/remove_public_key" + Cap::RemovePublicKey + end + guest_capability("openbsd", "rsync_install") do require_relative "cap/rsync" Cap::RSync diff --git a/plugins/guests/photon/cap/change_host_name.rb b/plugins/guests/photon/cap/change_host_name.rb new file mode 100644 index 000000000..5249dadba --- /dev/null +++ b/plugins/guests/photon/cap/change_host_name.rb @@ -0,0 +1,15 @@ +module VagrantPlugins + module GuestPhoton + module Cap + class ChangeHostName + def self.change_host_name(machine, name) + machine.communicate.tap do |comm| + unless comm.test("sudo hostname --fqdn | grep '#{name}'") + comm.sudo("hostname #{name.split('.')[0]}") + end + end + end + end + end + end +end diff --git a/plugins/guests/photon/cap/configure_networks.rb b/plugins/guests/photon/cap/configure_networks.rb new file mode 100644 index 000000000..50f4737b8 --- /dev/null +++ b/plugins/guests/photon/cap/configure_networks.rb @@ -0,0 +1,42 @@ +require 'tempfile' +require 'vagrant/util/template_renderer' + +module VagrantPlugins + module GuestPhoton + module Cap + class ConfigureNetworks + include Vagrant::Util + + def self.configure_networks(machine, networks) + machine.communicate.tap do |comm| + # Read network interface names + interfaces = [] + comm.sudo("ifconfig | grep 'eth' | cut -f1 -d' '") do |_, result| + interfaces = result.split("\n") + end + + # Configure interfaces + networks.each do |network| + comm.sudo("ifconfig #{interfaces[network[:interface].to_i]} #{network[:ip]} netmask #{network[:netmask]}") + end + + primary_machine_config = machine.env.active_machines.first + primary_machine = machine.env.machine(*primary_machine_config, true) + + get_ip = lambda do |machine| + ip = nil + machine.config.vm.networks.each do |type, opts| + if type == :private_network && opts[:ip] + ip = opts[:ip] + break + end + end + + ip + end + end + end + end + end + end +end diff --git a/plugins/guests/photon/cap/docker.rb b/plugins/guests/photon/cap/docker.rb new file mode 100644 index 000000000..954777908 --- /dev/null +++ b/plugins/guests/photon/cap/docker.rb @@ -0,0 +1,11 @@ +module VagrantPlugins + module GuestPhoton + module Cap + module Docker + def self.docker_daemon_running(machine) + machine.communicate.test('test -S /run/docker.sock') + end + end + end + end +end diff --git a/plugins/guests/photon/guest.rb b/plugins/guests/photon/guest.rb new file mode 100644 index 000000000..c33a9f8a2 --- /dev/null +++ b/plugins/guests/photon/guest.rb @@ -0,0 +1,9 @@ +module VagrantPlugins + module GuestPhoton + class Guest < Vagrant.plugin('2', :guest) + def detect?(machine) + machine.communicate.test("cat /etc/photon-release | grep 'VMware Photon Linux'") + end + end + end +end diff --git a/plugins/guests/photon/plugin.rb b/plugins/guests/photon/plugin.rb new file mode 100644 index 000000000..a56a0f44f --- /dev/null +++ b/plugins/guests/photon/plugin.rb @@ -0,0 +1,30 @@ +require 'vagrant' + +module VagrantPlugins + module GuestPhoton + class Plugin < Vagrant.plugin('2') + name 'VMware Photon guest' + description 'VMware Photon guest support.' + + guest('photon', 'linux') do + require File.expand_path("../guest", __FILE__) + Guest + end + + guest_capability('photon', 'change_host_name') do + require_relative 'cap/change_host_name' + Cap::ChangeHostName + end + + guest_capability('photon', 'configure_networks') do + require_relative 'cap/configure_networks' + Cap::ConfigureNetworks + end + + guest_capability('photon', 'docker_daemon_running') do + require_relative 'cap/docker' + Cap::Docker + end + end + end +end diff --git a/plugins/guests/redhat/cap/change_host_name.rb b/plugins/guests/redhat/cap/change_host_name.rb index 72ad47e8d..2520904ec 100644 --- a/plugins/guests/redhat/cap/change_host_name.rb +++ b/plugins/guests/redhat/cap/change_host_name.rb @@ -3,16 +3,7 @@ module VagrantPlugins module Cap class ChangeHostName def self.change_host_name(machine, name) - case machine.guest.capability("flavor") - when :rhel_7 - change_host_name_rhel7(machine, name) - else - new(machine, name).change! - end - end - - def self.change_host_name_rhel7(machine, name) - machine.communicate.sudo("hostnamectl set-hostname #{name}") + new(machine, name).change! end attr_reader :machine, :new_hostname @@ -25,11 +16,17 @@ module VagrantPlugins def change! return unless should_change? - update_sysconfig - update_hostname - update_etc_hosts - update_dhcp_hostnames - restart_networking + case machine.guest.capability("flavor") + when :rhel_7 + update_hostname_rhel7 + update_etc_hosts + else + update_sysconfig + update_hostname + update_etc_hosts + update_dhcp_hostnames + restart_networking + end end def should_change? @@ -61,6 +58,10 @@ module VagrantPlugins sudo "hostname #{fqdn}" end + def update_hostname_rhel7 + sudo "hostnamectl set-hostname #{fqdn}" + end + # /etc/hosts should resemble: # 127.0.0.1 host.fqdn.com host localhost ... def update_etc_hosts diff --git a/plugins/guests/redhat/cap/flavor.rb b/plugins/guests/redhat/cap/flavor.rb index b85810a06..2ae87201a 100644 --- a/plugins/guests/redhat/cap/flavor.rb +++ b/plugins/guests/redhat/cap/flavor.rb @@ -11,7 +11,7 @@ module VagrantPlugins output.chomp! # Detect various flavors we care about - if output =~ /(CentOS|Red Hat Enterprise) Linux( .+)? release 7/i + if output =~ /(CentOS|Red Hat Enterprise|Scientific) Linux( .+)? release 7/i return :rhel_7 else return :rhel diff --git a/plugins/guests/redhat/cap/nfs_client.rb b/plugins/guests/redhat/cap/nfs_client.rb index 98858f70f..14e69d53e 100644 --- a/plugins/guests/redhat/cap/nfs_client.rb +++ b/plugins/guests/redhat/cap/nfs_client.rb @@ -3,9 +3,27 @@ module VagrantPlugins module Cap class NFSClient def self.nfs_client_install(machine) - machine.communicate.tap do |comm| - comm.sudo("yum -y install nfs-utils nfs-utils-lib") - comm.sudo("/etc/init.d/rpcbind restart; /etc/init.d/nfs restart") + machine.communicate.sudo("yum -y install nfs-utils nfs-utils-lib") + restart_nfs(machine) + end + + def self.nfs_client_installed(machine) + installed = machine.communicate.test("test -x /sbin/mount.nfs") + restart_nfs(machine) if installed + installed + end + + protected + + def self.systemd? + `ps -o comm= 1`.chomp == 'systemd' + end + + def self.restart_nfs(machine) + if systemd? + machine.communicate.sudo("/bin/systemctl restart rpcbind nfs") + else + machine.communicate.sudo("/etc/init.d/rpcbind restart; /etc/init.d/nfs restart") end end end diff --git a/plugins/guests/redhat/plugin.rb b/plugins/guests/redhat/plugin.rb index f7a255599..96e641bcb 100644 --- a/plugins/guests/redhat/plugin.rb +++ b/plugins/guests/redhat/plugin.rb @@ -3,8 +3,8 @@ require "vagrant" module VagrantPlugins module GuestRedHat class Plugin < Vagrant.plugin("2") - name "RedHat guest" - description "RedHat guest support." + name "Red Hat Enterprise Linux guest" + description "Red Hat Enterprise Linux guest support." guest("redhat", "linux") do require File.expand_path("../guest", __FILE__) @@ -36,6 +36,11 @@ module VagrantPlugins Cap::NFSClient end + guest_capability("redhat", "nfs_client_installed") do + require_relative "cap/nfs_client" + Cap::NFSClient + end + guest_capability("redhat", "rsync_install") do require_relative "cap/rsync" Cap::RSync diff --git a/plugins/guests/solaris/cap/insert_public_key.rb b/plugins/guests/solaris/cap/insert_public_key.rb new file mode 100644 index 000000000..b0b13d041 --- /dev/null +++ b/plugins/guests/solaris/cap/insert_public_key.rb @@ -0,0 +1,22 @@ +require "vagrant/util/shell_quote" + +module VagrantPlugins + module GuestSolaris + module Cap + class InsertPublicKey + def self.insert_public_key(machine, contents) + # TODO: Code is identical to linux/cap/insert_public_key + 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 diff --git a/plugins/guests/solaris/cap/remove_public_key.rb b/plugins/guests/solaris/cap/remove_public_key.rb new file mode 100644 index 000000000..170d63870 --- /dev/null +++ b/plugins/guests/solaris/cap/remove_public_key.rb @@ -0,0 +1,22 @@ +require "vagrant/util/shell_quote" + +module VagrantPlugins + module GuestSolaris + module Cap + class RemovePublicKey + def self.remove_public_key(machine, contents) + # TODO: code is identical to linux/cap/remove_public_key + 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 diff --git a/plugins/guests/solaris/cap/rsync.rb b/plugins/guests/solaris/cap/rsync.rb index d060a8020..0d0220f40 100644 --- a/plugins/guests/solaris/cap/rsync.rb +++ b/plugins/guests/solaris/cap/rsync.rb @@ -17,10 +17,10 @@ module VagrantPlugins end def self.rsync_post(machine, opts) - su_cmd = machine.config.solaris.su_cmd + suexec_cmd = machine.config.solaris.suexec_cmd machine.communicate.execute( - "#{su_cmd} find '#{opts[:guestpath]}' '(' ! -user #{opts[:owner]} -or ! -group #{opts[:group]} ')' -print0 | " + - "xargs -0 -r chown #{opts[:owner]}:#{opts[:group]}") + "#{suexec_cmd} find '#{opts[:guestpath]}' '(' ! -user #{opts[:owner]} -or ! -group #{opts[:group]} ')' -print0 | " + + "xargs -0 chown #{opts[:owner]}:#{opts[:group]}") end end end diff --git a/plugins/guests/solaris/config.rb b/plugins/guests/solaris/config.rb index e0a6a486b..5983b6837 100644 --- a/plugins/guests/solaris/config.rb +++ b/plugins/guests/solaris/config.rb @@ -3,24 +3,28 @@ module VagrantPlugins class Config < Vagrant.plugin("2", :config) attr_accessor :halt_timeout attr_accessor :halt_check_interval - # This sets the command to use to execute items as a superuser. sudo is default + attr_accessor :suexec_cmd attr_accessor :device def initialize @halt_timeout = UNSET_VALUE @halt_check_interval = UNSET_VALUE - @suexec_cmd = 'sudo' - @device = "e1000g" + @suexec_cmd = UNSET_VALUE + @device = UNSET_VALUE end def finalize! if @halt_timeout != UNSET_VALUE puts "solaris.halt_timeout is deprecated and will be removed in Vagrant 1.7" end + if @halt_check_interval != UNSET_VALUE puts "solaris.halt_check_interval is deprecated and will be removed in Vagrant 1.7" end + + @suexec_cmd = "sudo" if @suexec_cmd == UNSET_VALUE + @device = "e1000g" if @device == UNSET_VALUE end end end diff --git a/plugins/guests/solaris/plugin.rb b/plugins/guests/solaris/plugin.rb index 31988453c..9f2b315ce 100644 --- a/plugins/guests/solaris/plugin.rb +++ b/plugins/guests/solaris/plugin.rb @@ -26,6 +26,11 @@ module VagrantPlugins Cap::ConfigureNetworks end + guest_capability("solaris", "insert_public_key") do + require_relative "cap/insert_public_key" + Cap::InsertPublicKey + end + guest_capability("solaris", "halt") do require_relative "cap/halt" Cap::Halt @@ -36,6 +41,11 @@ module VagrantPlugins Cap::MountVirtualBoxSharedFolder end + guest_capability("solaris", "remove_public_key") do + require_relative "cap/remove_public_key" + Cap::RemovePublicKey + end + guest_capability("solaris", "rsync_installed") do require_relative "cap/rsync" Cap::RSync diff --git a/plugins/guests/solaris11/cap/configure_networks.rb b/plugins/guests/solaris11/cap/configure_networks.rb index 92e47703a..15aa62679 100644 --- a/plugins/guests/solaris11/cap/configure_networks.rb +++ b/plugins/guests/solaris11/cap/configure_networks.rb @@ -19,6 +19,9 @@ module VagrantPlugins #machine.communicate.execute("#{ifconfig_cmd} up") #machine.communicate.execute("#{su_cmd} sh -c \"echo '#{network[:ip]}' > /etc/hostname.#{device}\"") # ipadm create-addr -T static -a local=172.16.10.15/24 net2/v4 + if machine.communicate.test("ipadm | grep #{device}/v4") + machine.communicate.execute("#{su_cmd} ipadm delete-addr #{device}/v4") + end machine.communicate.execute("#{su_cmd} ipadm create-addr -T static -a #{network[:ip]}/#{cidr} #{device}/v4") elsif network[:type].to_sym == :dhcp #machine.communicate.execute("#{ifconfig_cmd} dhcp start") diff --git a/plugins/guests/solaris11/cap/insert_public_key.rb b/plugins/guests/solaris11/cap/insert_public_key.rb new file mode 100644 index 000000000..55087c366 --- /dev/null +++ b/plugins/guests/solaris11/cap/insert_public_key.rb @@ -0,0 +1,21 @@ +require "vagrant/util/shell_quote" + +module VagrantPlugins + module GuestSolaris11 + 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 diff --git a/plugins/guests/solaris11/cap/remove_public_key.rb b/plugins/guests/solaris11/cap/remove_public_key.rb new file mode 100644 index 000000000..91924d3c5 --- /dev/null +++ b/plugins/guests/solaris11/cap/remove_public_key.rb @@ -0,0 +1,21 @@ +require "vagrant/util/shell_quote" + +module VagrantPlugins + module GuestSolaris11 + 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 diff --git a/plugins/guests/solaris11/cap/rsync.rb b/plugins/guests/solaris11/cap/rsync.rb index 039602172..95df046c9 100644 --- a/plugins/guests/solaris11/cap/rsync.rb +++ b/plugins/guests/solaris11/cap/rsync.rb @@ -17,10 +17,10 @@ module VagrantPlugins end def self.rsync_post(machine, opts) - su_cmd = machine.config.solaris11.su_cmd + suexec_cmd = machine.config.solaris11.suexec_cmd machine.communicate.execute( - "#{su_cmd} '#{opts[:guestpath]}' '(' ! -user #{opts[:owner]} -or ! -group #{opts[:group]} ')' -print0 | " + - "xargs -0 -r chown #{opts[:owner]}:#{opts[:group]}") + "#{suexec_cmd} '#{opts[:guestpath]}' '(' ! -user #{opts[:owner]} -or ! -group #{opts[:group]} ')' -print0 | " + + "xargs -0 chown #{opts[:owner]}:#{opts[:group]}") end end end diff --git a/plugins/guests/solaris11/config.rb b/plugins/guests/solaris11/config.rb index 935e0d9b9..a6051fa95 100644 --- a/plugins/guests/solaris11/config.rb +++ b/plugins/guests/solaris11/config.rb @@ -14,8 +14,8 @@ module VagrantPlugins def initialize @halt_timeout = UNSET_VALUE @halt_check_interval = UNSET_VALUE - @suexec_cmd = 'sudo' - @device = "net" + @suexec_cmd = UNSET_VALUE + @device = UNSET_VALUE end def finalize! @@ -25,6 +25,9 @@ module VagrantPlugins if @halt_check_interval != UNSET_VALUE puts "solaris11.halt_check_interval is deprecated and will be removed in Vagrant 1.7" end + + @suexec_cmd = "sudo" if @suexec_cmd == UNSET_VALUE + @device = "net" if @device == UNSET_VALUE end end end diff --git a/plugins/guests/solaris11/plugin.rb b/plugins/guests/solaris11/plugin.rb index 707fd2bfc..1a5d07451 100644 --- a/plugins/guests/solaris11/plugin.rb +++ b/plugins/guests/solaris11/plugin.rb @@ -49,6 +49,16 @@ module VagrantPlugins require_relative "cap/rsync" Cap::RSync end + + guest_capability("solaris11", "insert_public_key") do + require_relative "cap/insert_public_key" + Cap::InsertPublicKey + end + + guest_capability("solaris11", "remove_public_key") do + require_relative "cap/remove_public_key" + Cap::RemovePublicKey + end end end end diff --git a/plugins/guests/suse/cap/change_host_name.rb b/plugins/guests/suse/cap/change_host_name.rb index a8ba242d7..616b92495 100644 --- a/plugins/guests/suse/cap/change_host_name.rb +++ b/plugins/guests/suse/cap/change_host_name.rb @@ -1,5 +1,5 @@ module VagrantPlugins - module GuestSuse + module GuestSUSE module Cap class ChangeHostName def self.change_host_name(machine, name) @@ -8,6 +8,7 @@ module VagrantPlugins if !comm.test("sudo hostname | grep '#{name}'") comm.sudo("echo #{name} > /etc/HOSTNAME") comm.sudo("hostname #{name}") + comm.sudo("sed -i 's@^\\(127[.]0[.]0[.]1[[:space:]]\\+\\)@\\1#{name} #{name.split('.')[0]} @' /etc/hosts") end end diff --git a/plugins/guests/suse/cap/configure_networks.rb b/plugins/guests/suse/cap/configure_networks.rb index d60bb8306..00902dbd0 100644 --- a/plugins/guests/suse/cap/configure_networks.rb +++ b/plugins/guests/suse/cap/configure_networks.rb @@ -5,7 +5,7 @@ require "vagrant/util/retryable" require "vagrant/util/template_renderer" module VagrantPlugins - module GuestSuse + module GuestSUSE module Cap class ConfigureNetworks extend Vagrant::Util::Retryable diff --git a/plugins/guests/suse/cap/halt.rb b/plugins/guests/suse/cap/halt.rb index ea1957594..e6b20818b 100644 --- a/plugins/guests/suse/cap/halt.rb +++ b/plugins/guests/suse/cap/halt.rb @@ -1,5 +1,5 @@ module VagrantPlugins - module GuestSuse + module GuestSUSE module Cap class Halt def self.halt(machine) diff --git a/plugins/guests/suse/cap/network_scripts_dir.rb b/plugins/guests/suse/cap/network_scripts_dir.rb index d8c34b290..82b2e7f4a 100644 --- a/plugins/guests/suse/cap/network_scripts_dir.rb +++ b/plugins/guests/suse/cap/network_scripts_dir.rb @@ -1,9 +1,9 @@ module VagrantPlugins - module GuestSuse + module GuestSUSE module Cap class NetworkScriptsDir def self.network_scripts_dir(machine) - "/etc/sysconfig/network/" + "/etc/sysconfig/network" end end end diff --git a/plugins/guests/suse/cap/nfs_client.rb b/plugins/guests/suse/cap/nfs_client.rb new file mode 100644 index 000000000..36a92d201 --- /dev/null +++ b/plugins/guests/suse/cap/nfs_client.rb @@ -0,0 +1,16 @@ +module VagrantPlugins + module GuestSUSE + module Cap + class NFSClient + def self.nfs_client_install(machine) + machine.communicate.tap do |comm| + comm.sudo("zypper -n install nfs-client") + + comm.sudo("/sbin/service rpcbind restart") + comm.sudo("/sbin/service nfs restart") + end + end + end + end + end +end diff --git a/plugins/guests/suse/cap/rsync.rb b/plugins/guests/suse/cap/rsync.rb new file mode 100644 index 000000000..aed989613 --- /dev/null +++ b/plugins/guests/suse/cap/rsync.rb @@ -0,0 +1,17 @@ +module VagrantPlugins + module GuestSUSE + module Cap + class RSync + def self.rsync_installed(machine) + machine.communicate.test("test -f /usr/bin/rsync") + end + + def self.rsync_install(machine) + machine.communicate.tap do |comm| + comm.sudo("zypper -n install rsync") + end + end + end + end + end +end diff --git a/plugins/guests/suse/guest.rb b/plugins/guests/suse/guest.rb index 340f5818d..643403f19 100644 --- a/plugins/guests/suse/guest.rb +++ b/plugins/guests/suse/guest.rb @@ -1,10 +1,10 @@ require "vagrant" module VagrantPlugins - module GuestSuse + module GuestSUSE class Guest < Vagrant.plugin("2", :guest) def detect?(machine) - machine.communicate.test("cat /etc/SuSE-release") + machine.communicate.test("test -f /etc/SuSE-release || grep -q SUSE /etc/os-release") end end end diff --git a/plugins/guests/suse/plugin.rb b/plugins/guests/suse/plugin.rb index 0aa9bd289..3850ae45c 100644 --- a/plugins/guests/suse/plugin.rb +++ b/plugins/guests/suse/plugin.rb @@ -1,12 +1,12 @@ require "vagrant" module VagrantPlugins - module GuestSuse + module GuestSUSE class Plugin < Vagrant.plugin("2") name "SUSE guest" description "SUSE guest support." - guest("suse", "redhat") do + guest("suse", "linux") do require File.expand_path("../guest", __FILE__) Guest end @@ -16,20 +16,35 @@ module VagrantPlugins Cap::ChangeHostName end - guest_capability("suse", "halt") do - require_relative "cap/halt" - Cap::Halt - end - guest_capability("suse", "configure_networks") do require_relative "cap/configure_networks" Cap::ConfigureNetworks end + guest_capability("suse", "halt") do + require_relative "cap/halt" + Cap::Halt + end + guest_capability("suse", "network_scripts_dir") do require_relative "cap/network_scripts_dir" Cap::NetworkScriptsDir end + + guest_capability("suse", "nfs_client_install") do + require_relative "cap/nfs_client" + Cap::NFSClient + end + + guest_capability("suse", "rsync_install") do + require_relative "cap/rsync" + Cap::RSync + end + + guest_capability("suse", "rsync_installed") do + require_relative "cap/rsync" + Cap::RSync + end end end end diff --git a/plugins/guests/tinycore/cap/change_host_name.rb b/plugins/guests/tinycore/cap/change_host_name.rb new file mode 100644 index 000000000..1c4ec8590 --- /dev/null +++ b/plugins/guests/tinycore/cap/change_host_name.rb @@ -0,0 +1,13 @@ +module VagrantPlugins + module GuestTinyCore + module Cap + class ChangeHostName + def self.change_host_name(machine, name) + if !machine.communicate.test("hostname | grep '^#{name}$'") + machine.communicate.sudo("/usr/bin/sethostname #{name}") + end + end + end + end + end +end diff --git a/plugins/guests/tinycore/cap/configure_networks.rb b/plugins/guests/tinycore/cap/configure_networks.rb index cccc0e311..3e267748a 100644 --- a/plugins/guests/tinycore/cap/configure_networks.rb +++ b/plugins/guests/tinycore/cap/configure_networks.rb @@ -7,6 +7,11 @@ module VagrantPlugins def self.configure_networks(machine, networks) machine.communicate.tap do |comm| networks.each do |n| + if n[:type] == :dhcp + comm.sudo("/sbin/udhcpc -b -i eth#{n[:interface]} -p /var/run/udhcpc.eth#{n[:interface]}.pid") + return + end + ifc = "/sbin/ifconfig eth#{n[:interface]}" broadcast = (IPAddr.new(n[:ip]) | (~ IPAddr.new(n[:netmask]))).to_s comm.sudo("#{ifc} down") diff --git a/plugins/guests/tinycore/cap/rsync.rb b/plugins/guests/tinycore/cap/rsync.rb index 7ee3c8fc1..674ba349b 100644 --- a/plugins/guests/tinycore/cap/rsync.rb +++ b/plugins/guests/tinycore/cap/rsync.rb @@ -4,8 +4,11 @@ module VagrantPlugins class RSync def self.rsync_install(machine) machine.communicate.tap do |comm| - # do not sudo tce-load - comm.execute("tce-load -wi rsync") + # Run it but don't error check because this is always failing currently + comm.execute("tce-load -wi acl attr rsync", error_check: false) + + # Verify it by executing rsync + comm.execute("rsync --help") end end end diff --git a/plugins/guests/tinycore/plugin.rb b/plugins/guests/tinycore/plugin.rb index b7553fa0a..3febbb16f 100644 --- a/plugins/guests/tinycore/plugin.rb +++ b/plugins/guests/tinycore/plugin.rb @@ -16,6 +16,11 @@ module VagrantPlugins Cap::ConfigureNetworks end + guest_capability("tinycore", "change_host_name") do + require_relative "cap/change_host_name" + Cap::ChangeHostName + end + guest_capability("tinycore", "halt") do require_relative "cap/halt" Cap::Halt diff --git a/plugins/guests/ubuntu/cap/change_host_name.rb b/plugins/guests/ubuntu/cap/change_host_name.rb index beb84d739..706d4850a 100644 --- a/plugins/guests/ubuntu/cap/change_host_name.rb +++ b/plugins/guests/ubuntu/cap/change_host_name.rb @@ -6,22 +6,39 @@ module VagrantPlugins super end + def update_etc_hostname + return super unless vivid? + sudo("hostnamectl set-hostname '#{short_hostname}'") + end + def refresh_hostname_service if hardy? # hostname.sh returns 1, so use `true` to get a 0 exitcode sudo("/etc/init.d/hostname.sh start; true") + elsif vivid? + # Service runs via hostnamectl else sudo("service hostname start") end end def hardy? - machine.communicate.test("[ `lsb_release -c -s` = hardy ]") + os_version("hardy") + end + + def vivid? + os_version("vivid") end def renew_dhcp sudo("ifdown -a; ifup -a; ifup -a --allow=hotplug") end + + private + + def os_version(name) + machine.communicate.test("[ `lsb_release -c -s` = #{name} ]") + end end end end diff --git a/plugins/guests/ubuntu/guest.rb b/plugins/guests/ubuntu/guest.rb index 9e9a9a9ad..9295ede22 100644 --- a/plugins/guests/ubuntu/guest.rb +++ b/plugins/guests/ubuntu/guest.rb @@ -4,7 +4,7 @@ module VagrantPlugins module GuestUbuntu class Guest < Vagrant.plugin("2", :guest) def detect?(machine) - machine.communicate.test("cat /etc/issue | grep 'Ubuntu'") + machine.communicate.test("[ -x /usr/bin/lsb_release ] && /usr/bin/lsb_release -i 2>/dev/null | grep Ubuntu") end end end diff --git a/plugins/guests/windows/cap/choose_addressable_ip_addr.rb b/plugins/guests/windows/cap/choose_addressable_ip_addr.rb new file mode 100644 index 000000000..2304246ce --- /dev/null +++ b/plugins/guests/windows/cap/choose_addressable_ip_addr.rb @@ -0,0 +1,20 @@ +module VagrantPlugins + module GuestWindows + module Cap + module ChooseAddressableIPAddr + def self.choose_addressable_ip_addr(machine, possible) + machine.communicate.tap do |comm| + possible.each do |ip| + command = "ping -n 1 -w 1 #{ip}" + if comm.test(command) + return ip + end + end + end + + nil + end + end + end + end +end diff --git a/plugins/guests/windows/cap/configure_networks.rb b/plugins/guests/windows/cap/configure_networks.rb index 8130cef77..8e766a319 100644 --- a/plugins/guests/windows/cap/configure_networks.rb +++ b/plugins/guests/windows/cap/configure_networks.rb @@ -64,7 +64,9 @@ module VagrantPlugins guest_network.network_adapters.each do |nic| @@logger.debug("nic: #{nic.inspect}") naked_mac = nic[:mac_address].gsub(':','') - if driver_mac_address[naked_mac] + # If the :net_connection_id entry is nil then it is probably a virtual connection + # and should be ignored. + if driver_mac_address[naked_mac] && !nic[:net_connection_id].nil? vm_interface_map[driver_mac_address[naked_mac]] = { net_connection_id: nic[:net_connection_id], mac_address: naked_mac, diff --git a/plugins/guests/windows/cap/mount_shared_folder.rb b/plugins/guests/windows/cap/mount_shared_folder.rb index 8f7866a1a..4329025f6 100644 --- a/plugins/guests/windows/cap/mount_shared_folder.rb +++ b/plugins/guests/windows/cap/mount_shared_folder.rb @@ -16,6 +16,11 @@ module VagrantPlugins mount_shared_folder(machine, name, guestpath, "\\\\psf\\") end + def self.mount_smb_shared_folder(machine, name, guestpath, options) + machine.communicate.execute("cmdkey /add:#{options[:smb_host]} /user:#{options[:smb_username]} /pass:#{options[:smb_password]}", {shell: :powershell, elevated: true}) + mount_shared_folder(machine, name, guestpath, "\\\\#{options[:smb_host]}\\") + end + protected def self.mount_shared_folder(machine, name, guestpath, vm_provider_unc_base) diff --git a/plugins/guests/windows/cap/rsync.rb b/plugins/guests/windows/cap/rsync.rb new file mode 100644 index 000000000..e391b92db --- /dev/null +++ b/plugins/guests/windows/cap/rsync.rb @@ -0,0 +1,13 @@ +module VagrantPlugins + module GuestWindows + module Cap + class RSync + def self.rsync_pre(machine, opts) + machine.communicate.tap do |comm| + comm.execute("mkdir '#{opts[:guestpath]}'") + end + end + end + end + end +end diff --git a/plugins/guests/windows/plugin.rb b/plugins/guests/windows/plugin.rb index fa1408314..47ca67e3f 100644 --- a/plugins/guests/windows/plugin.rb +++ b/plugins/guests/windows/plugin.rb @@ -54,6 +54,21 @@ module VagrantPlugins Cap::Reboot end + guest_capability(:windows, :choose_addressable_ip_addr) do + require_relative "cap/choose_addressable_ip_addr" + Cap::ChooseAddressableIPAddr + end + + guest_capability(:windows, :mount_smb_shared_folder) do + require_relative "cap/mount_shared_folder" + Cap::MountSharedFolder + end + + guest_capability(:windows, :rsync_pre) do + require_relative "cap/rsync" + Cap::RSync + end + protected def self.init! diff --git a/plugins/hosts/bsd/cap/nfs.rb b/plugins/hosts/bsd/cap/nfs.rb index df37f559d..5d31c3cc1 100644 --- a/plugins/hosts/bsd/cap/nfs.rb +++ b/plugins/hosts/bsd/cap/nfs.rb @@ -102,10 +102,16 @@ module VagrantPlugins # First, clean up the old entry nfs_cleanup(id) + # Only use "sudo" if we can't write to /etc/exports directly + sudo_command = "" + sudo_command = "sudo " if !File.writable?("/etc/exports") + # Output the rendered template into the exports output.split("\n").each do |line| line = Vagrant::Util::ShellQuote.escape(line, "'") - system("echo '#{line}' | sudo tee -a /etc/exports >/dev/null") + system( + "echo '#{line}' | " + + "#{sudo_command}tee -a /etc/exports >/dev/null") end # We run restart here instead of "update" just in case nfsd @@ -131,7 +137,7 @@ module VagrantPlugins user = Process.uid File.read("/etc/exports").lines.each do |line| - if id = line[/^# VAGRANT-BEGIN:( #{user})? ([A-Za-z0-9-]+?)$/, 2] + if id = line[/^# VAGRANT-BEGIN:( #{user})? ([\.\/A-Za-z0-9\-_:]+?)$/, 2] if valid_ids.include?(id) logger.debug("Valid ID: #{id}") else @@ -165,12 +171,19 @@ module VagrantPlugins user = Process.uid + command = [] + command << "sudo" if !File.writable?("/etc/exports") + command += [ + "sed", "-E", "-e", + "/^# VAGRANT-BEGIN:( #{user})? #{id}/," + + "/^# VAGRANT-END:( #{user})? #{id}/ d", + "-ibak", + "/etc/exports" + ] + # Use sed to just strip out the block of code which was inserted # by Vagrant, and restart NFS. - system( - "sudo", "sed", "-E", "-e", - "/^# VAGRANT-BEGIN:( #{user})? #{id}/,/^# VAGRANT-END:( #{user})? #{id}/ d", - "-ibak", "/etc/exports") + system(*command) end def self.nfs_checkexports! diff --git a/plugins/hosts/linux/cap/nfs.rb b/plugins/hosts/linux/cap/nfs.rb index 6b1c95c43..a55d8acb2 100644 --- a/plugins/hosts/linux/cap/nfs.rb +++ b/plugins/hosts/linux/cap/nfs.rb @@ -38,9 +38,13 @@ module VagrantPlugins nfs_cleanup(id) + # Only use "sudo" if we can't write to /etc/exports directly + sudo_command = "" + sudo_command = "sudo " if !File.writable?("/etc/exports") + output.split("\n").each do |line| line = Vagrant::Util::ShellQuote.escape(line, "'") - system(%Q[echo '#{line}' | sudo tee -a /etc/exports >/dev/null]) + system(%Q[echo '#{line}' | #{sudo_command}tee -a /etc/exports >/dev/null]) end if nfs_running?(nfs_check_command) @@ -67,7 +71,7 @@ module VagrantPlugins user = Process.uid File.read("/etc/exports").lines.each do |line| - if id = line[/^# VAGRANT-BEGIN:( #{user})? ([A-Za-z0-9-]+?)$/, 2] + if id = line[/^# VAGRANT-BEGIN:( #{user})? ([\.\/A-Za-z0-9\-_:]+?)$/, 2] if valid_ids.include?(id) logger.debug("Valid ID: #{id}") else @@ -92,9 +96,13 @@ module VagrantPlugins user = Regexp.escape(Process.uid.to_s) id = Regexp.escape(id.to_s) + # Only use "sudo" if we can't write to /etc/exports directly + sudo_command = "" + sudo_command = "sudo " if !File.writable?("/etc/exports") + # Use sed to just strip out the block of code which was inserted # by Vagrant - system("sudo sed -r -e '\\\x01^# VAGRANT-BEGIN:( #{user})? #{id}\x01,\\\x01^# VAGRANT-END:( #{user})? #{id}\x01 d' -ibak /etc/exports") + system("cp /etc/exports $TMPDIR && #{sudo_command}sed -r -e '\\\x01^# VAGRANT-BEGIN:( #{user})? #{id}\x01,\\\x01^# VAGRANT-END:( #{user})? #{id}\x01 d' -ibak $TMPDIR/exports ; cp $TMPDIR/exports /etc/exports") end def self.nfs_opts_setup(folders) diff --git a/plugins/hosts/opensuse/host.rb b/plugins/hosts/opensuse/host.rb deleted file mode 100644 index 5e0240d27..000000000 --- a/plugins/hosts/opensuse/host.rb +++ /dev/null @@ -1,21 +0,0 @@ -require "pathname" - -require "vagrant" - -module VagrantPlugins - module HostOpenSUSE - class Host < Vagrant.plugin("2", :host) - def detect?(env) - release_file = Pathname.new("/etc/SuSE-release") - - if release_file.exist? - release_file.open("r") do |f| - return true if f.gets =~ /^openSUSE/ - end - end - - false - end - end - end -end diff --git a/plugins/hosts/opensuse/plugin.rb b/plugins/hosts/opensuse/plugin.rb deleted file mode 100644 index 6ed81f8da..000000000 --- a/plugins/hosts/opensuse/plugin.rb +++ /dev/null @@ -1,27 +0,0 @@ -require "vagrant" - -module VagrantPlugins - module HostOpenSUSE - class Plugin < Vagrant.plugin("2") - name "OpenSUSE host" - description "OpenSUSE host support." - - host("opensuse", "linux") do - require_relative "host" - Host - end - - # Linux-specific helpers we need to determine paths that can - # be overriden. - host_capability("opensuse", "nfs_check_command") do - require_relative "cap/nfs" - Cap::NFS - end - - host_capability("opensuse", "nfs_start_command") do - require_relative "cap/nfs" - Cap::NFS - end - end - end -end diff --git a/plugins/hosts/redhat/plugin.rb b/plugins/hosts/redhat/plugin.rb index 8dae68705..00c2b5941 100644 --- a/plugins/hosts/redhat/plugin.rb +++ b/plugins/hosts/redhat/plugin.rb @@ -3,8 +3,8 @@ require "vagrant" module VagrantPlugins module HostRedHat class Plugin < Vagrant.plugin("2") - name "Red Hat host" - description "Red Hat host support." + name "Red Hat Enterprise Linux host" + description "Red Hat Enterprise Linux host support." host("redhat", "linux") do require File.expand_path("../host", __FILE__) diff --git a/plugins/hosts/opensuse/cap/nfs.rb b/plugins/hosts/suse/cap/nfs.rb similarity index 58% rename from plugins/hosts/opensuse/cap/nfs.rb rename to plugins/hosts/suse/cap/nfs.rb index d16bc9327..42bde6bb2 100644 --- a/plugins/hosts/opensuse/cap/nfs.rb +++ b/plugins/hosts/suse/cap/nfs.rb @@ -1,9 +1,13 @@ module VagrantPlugins - module HostOpenSUSE + module HostSUSE module Cap class NFS + def self.nfs_installed(env) + system("rpm -q nfs-kernel-server > /dev/null 2>&1") + end + def self.nfs_check_command(env) - "/sbin/service nfsserver status" + "pidof nfsd > /dev/null" end def self.nfs_start_command(env) diff --git a/plugins/hosts/suse/host.rb b/plugins/hosts/suse/host.rb new file mode 100644 index 000000000..2cc5c24c7 --- /dev/null +++ b/plugins/hosts/suse/host.rb @@ -0,0 +1,29 @@ +require "pathname" + +require "vagrant" + +module VagrantPlugins + module HostSUSE + class Host < Vagrant.plugin("2", :host) + def detect?(env) + old_release_file = Pathname.new("/etc/SuSE-release") + + if old_release_file.exist? + old_release_file.open("r") do |f| + return true if f.gets =~ /^(openSUSE|SUSE Linux Enterprise)/ + end + end + + new_release_file = Pathname.new("/etc/os-release") + + if new_release_file.exist? + new_release_file.open("r") do |f| + return true if f.gets =~ /(openSUSE|SLES)/ + end + end + + false + end + end + end +end diff --git a/plugins/hosts/suse/plugin.rb b/plugins/hosts/suse/plugin.rb new file mode 100644 index 000000000..3aa1b441f --- /dev/null +++ b/plugins/hosts/suse/plugin.rb @@ -0,0 +1,30 @@ +require "vagrant" + +module VagrantPlugins + module HostSUSE + class Plugin < Vagrant.plugin("2") + name "SUSE host" + description "SUSE host support." + + host("suse", "linux") do + require_relative "host" + Host + end + + host_capability("suse", "nfs_installed") do + require_relative "cap/nfs" + Cap::NFS + end + + host_capability("suse", "nfs_check_command") do + require_relative "cap/nfs" + Cap::NFS + end + + host_capability("suse", "nfs_start_command") do + require_relative "cap/nfs" + Cap::NFS + end + end + end +end diff --git a/plugins/hosts/windows/cap/rdp.rb b/plugins/hosts/windows/cap/rdp.rb index 9976834de..8ddc67117 100644 --- a/plugins/hosts/windows/cap/rdp.rb +++ b/plugins/hosts/windows/cap/rdp.rb @@ -10,7 +10,6 @@ module VagrantPlugins def self.rdp_client(env, rdp_info) config = nil opts = { - "drivestoredirect:s" => "*", "full address:s" => "#{rdp_info[:host]}:#{rdp_info[:port]}", "prompt for credentials:i" => "1", "username:s" => rdp_info[:username], diff --git a/plugins/kernel_v2/config/push.rb b/plugins/kernel_v2/config/push.rb new file mode 100644 index 000000000..ccba3826d --- /dev/null +++ b/plugins/kernel_v2/config/push.rb @@ -0,0 +1,143 @@ +require "vagrant" + +module VagrantPlugins + module Kernel_V2 + class PushConfig < Vagrant.plugin("2", :config) + VALID_OPTIONS = [:strategy].freeze + + attr_accessor :name + + def initialize + @logger = Log4r::Logger.new("vagrant::config::push") + + # Internal state + @__defined_pushes = {} + @__compiled_pushes = {} + @__finalized = false + end + + def finalize! + @logger.debug("finalizing") + + # Compile all the provider configurations + @__defined_pushes.each do |name, tuples| + # Capture the strategy so we can use it later. This will be used in + # the block iteration for merging/overwriting + strategy = name + strategy = tuples[0][0] if tuples[0] + + # Find the configuration class for this push + config_class = Vagrant.plugin("2").manager.push_configs[strategy] + config_class ||= Vagrant::Config::V2::DummyConfig + + # Load it up + config = config_class.new + + begin + tuples.each do |s, b| + # Update the strategy if it has changed, reseting the current + # config object. + if s != strategy + @logger.warn("duplicate strategy defined, overwriting config") + strategy = s + config = config_class.new + end + + # If we don't have any blocks, then ignore it + next if b.nil? + + new_config = config_class.new + b.call(new_config, Vagrant::Config::V2::DummyConfig.new) + config = config.merge(new_config) + end + rescue Exception => e + raise Vagrant::Errors::VagrantfileLoadError, + path: "", + message: e.message + end + + config.finalize! + + # Store it for retrieval later + @__compiled_pushes[name] = [strategy, config] + end + + @__finalized = true + end + + # Define a new push in the Vagrantfile with the given name. + # + # @example + # vm.push.define "ftp" + # + # @example + # vm.push.define "ftp" do |s| + # s.host = "..." + # end + # + # @example + # vm.push.define "production", strategy: "docker" do |s| + # # ... + # end + # + # @param [#to_sym] name The name of the this strategy. By default, this + # is also the name of the strategy, but the `:strategy` key can be given + # to customize this behavior + # @param [Hash] options The list of options + # + def define(name, **options, &block) + name = name.to_sym + strategy = options[:strategy] || name + + @__defined_pushes[name] ||= [] + @__defined_pushes[name] << [strategy.to_sym, block] + end + + # The String representation of this Push. + # + # @return [String] + def to_s + "Push" + end + + # Custom merge method + def merge(other) + super.tap do |result| + other_pushes = other.instance_variable_get(:@__defined_pushes) + new_pushes = @__defined_pushes.dup + + other_pushes.each do |key, tuples| + new_pushes[key] ||= [] + new_pushes[key] += tuples + end + + result.instance_variable_set(:@__defined_pushes, new_pushes) + end + end + + # Validate all pushes + def validate(machine) + errors = { "push" => _detected_errors } + + __compiled_pushes.each do |_, push| + config = push[1] + push_errors = config.validate(machine) + + if push_errors + errors = Vagrant::Config::V2::Util.merge_errors(errors, push_errors) + end + end + + errors + end + + # This returns the list of compiled pushes as a hash by name. + # + # @return [Hash>] + def __compiled_pushes + raise "Must finalize first!" if !@__finalized + @__compiled_pushes.dup + end + end + end +end diff --git a/plugins/kernel_v2/config/ssh.rb b/plugins/kernel_v2/config/ssh.rb index 4040e228b..95603142c 100644 --- a/plugins/kernel_v2/config/ssh.rb +++ b/plugins/kernel_v2/config/ssh.rb @@ -11,7 +11,9 @@ module VagrantPlugins attr_accessor :keep_alive attr_accessor :shell attr_accessor :proxy_command + attr_accessor :ssh_command attr_accessor :pty + attr_accessor :sudo_command attr_reader :default @@ -23,8 +25,10 @@ module VagrantPlugins @guest_port = UNSET_VALUE @keep_alive = UNSET_VALUE @proxy_command = UNSET_VALUE + @ssh_command = UNSET_VALUE @pty = UNSET_VALUE @shell = UNSET_VALUE + @sudo_command = UNSET_VALUE @default = SSHConnectConfig.new end @@ -44,9 +48,14 @@ module VagrantPlugins @guest_port = 22 if @guest_port == UNSET_VALUE @keep_alive = true if @keep_alive == UNSET_VALUE @proxy_command = nil if @proxy_command == UNSET_VALUE + @ssh_command = nil if @ssh_command == UNSET_VALUE @pty = false if @pty == UNSET_VALUE @shell = "bash -l" if @shell == UNSET_VALUE + if @sudo_command == UNSET_VALUE + @sudo_command = "sudo -E -H %c" + end + @default.username = "vagrant" if @default.username == UNSET_VALUE @default.port = @guest_port if @default.port == UNSET_VALUE @default.finalize! diff --git a/plugins/kernel_v2/config/vm.rb b/plugins/kernel_v2/config/vm.rb index 31af48df7..2de1b343f 100644 --- a/plugins/kernel_v2/config/vm.rb +++ b/plugins/kernel_v2/config/vm.rb @@ -14,6 +14,7 @@ module VagrantPlugins class VMConfig < Vagrant.plugin("2", :config) DEFAULT_VM_NAME = :default + attr_accessor :allowed_synced_folder_types attr_accessor :base_mac attr_accessor :boot_timeout attr_accessor :box @@ -27,6 +28,7 @@ module VagrantPlugins attr_accessor :box_download_checksum_type attr_accessor :box_download_client_cert attr_accessor :box_download_insecure + attr_accessor :box_download_location_trusted attr_accessor :communicator attr_accessor :graceful_halt_timeout attr_accessor :guest @@ -36,25 +38,29 @@ module VagrantPlugins attr_reader :provisioners def initialize - @base_mac = UNSET_VALUE - @boot_timeout = UNSET_VALUE - @box = UNSET_VALUE - @box_check_update = UNSET_VALUE - @box_download_ca_cert = UNSET_VALUE - @box_download_ca_path = UNSET_VALUE - @box_download_checksum = UNSET_VALUE - @box_download_checksum_type = UNSET_VALUE - @box_download_client_cert = UNSET_VALUE - @box_download_insecure = UNSET_VALUE - @box_url = UNSET_VALUE - @box_version = UNSET_VALUE - @communicator = UNSET_VALUE - @graceful_halt_timeout = UNSET_VALUE - @guest = UNSET_VALUE - @hostname = UNSET_VALUE - @post_up_message = UNSET_VALUE - @provisioners = [] - @usable_port_range = UNSET_VALUE + @logger = Log4r::Logger.new("vagrant::config::vm") + + @allowed_synced_folder_types = UNSET_VALUE + @base_mac = UNSET_VALUE + @boot_timeout = UNSET_VALUE + @box = UNSET_VALUE + @box_check_update = UNSET_VALUE + @box_download_ca_cert = UNSET_VALUE + @box_download_ca_path = UNSET_VALUE + @box_download_checksum = UNSET_VALUE + @box_download_checksum_type = UNSET_VALUE + @box_download_client_cert = UNSET_VALUE + @box_download_insecure = UNSET_VALUE + @box_download_location_trusted = UNSET_VALUE + @box_url = UNSET_VALUE + @box_version = UNSET_VALUE + @communicator = UNSET_VALUE + @graceful_halt_timeout = UNSET_VALUE + @guest = UNSET_VALUE + @hostname = UNSET_VALUE + @post_up_message = UNSET_VALUE + @provisioners = [] + @usable_port_range = UNSET_VALUE # Internal state @__compiled_provider_configs = {} @@ -63,6 +69,7 @@ module VagrantPlugins @__finalized = false @__networks = {} @__providers = {} + @__provider_order = [] @__provider_overrides = {} @__synced_folders = {} end @@ -95,7 +102,7 @@ module VagrantPlugins end other_defined_vms.each do |key, subvm| - if !new_defined_vms.has_key?(key) + if !new_defined_vms.key?(key) new_defined_vms[key] = subvm.clone else new_defined_vms[key].config_procs.concat(subvm.config_procs) @@ -112,6 +119,12 @@ module VagrantPlugins new_providers[key] += blocks end + # Merge the provider ordering. Anything defined in our CURRENT + # scope is before anything else. + other_order = other.instance_variable_get(:@__provider_order) + new_order = @__provider_order.dup + new_order = (new_order + other_order).uniq + # Merge the provider overrides by appending them... other_overrides = other.instance_variable_get(:@__provider_overrides) new_overrides = @__provider_overrides.dup @@ -125,8 +138,8 @@ module VagrantPlugins new_provs = [] other_provs = other.provisioners.dup @provisioners.each do |p| - if p.id - other_p = other_provs.find { |o| p.id == o.id } + if p.name + other_p = other_provs.find { |o| p.name == o.name } if other_p # There is an override. Take it. other_p.config = p.config.merge(other_p.config) @@ -162,6 +175,7 @@ module VagrantPlugins result.instance_variable_set(:@__defined_vm_keys, new_defined_vm_keys) result.instance_variable_set(:@__defined_vms, new_defined_vms) result.instance_variable_set(:@__providers, new_providers) + result.instance_variable_set(:@__provider_order, new_order) result.instance_variable_set(:@__provider_overrides, new_overrides) result.instance_variable_set(:@__synced_folders, new_folders) end @@ -189,7 +203,7 @@ module VagrantPlugins options ||= {} options[:guestpath] = guestpath.to_s.gsub(/\/$/, '') options[:hostpath] = hostpath - options[:disabled] = false if !options.has_key?(:disabled) + options[:disabled] = false if !options.key?(:disabled) options = (@__synced_folders[options[:guestpath]] || {}). merge(options.dup) @@ -239,7 +253,7 @@ module VagrantPlugins id = "#{type}-#{id}" # Merge in the previous settings if we have them. - if @__networks.has_key?(id) + if @__networks.key?(id) options = @__networks[id][1].merge(options) end @@ -255,6 +269,9 @@ module VagrantPlugins @__providers[name] ||= [] @__provider_overrides[name] ||= [] + # Add the provider to the ordering list + @__provider_order << name + if block_given? @__providers[name] << block if block_given? @@ -267,21 +284,36 @@ module VagrantPlugins end def provision(name, **options, &block) - id = options.delete(:id).to_s if options.has_key?(:id) + type = name + if options.key?(:type) + type = options.delete(:type) + else + name = nil + end + + if options.key?(:id) + puts "Setting `id` on a provisioner is deprecated. Please use the" + puts "new syntax of `config.vm.provision \"name\", type: \"type\"" + puts "where \"name\" is the replacement for `id`. This will be" + puts "fully removed in Vagrant 1.8." + + name = id + end prov = nil - if id - prov = @provisioners.find { |p| p.id == id } + if name + name = name.to_sym + prov = @provisioners.find { |p| p.name == name } end if !prov - prov = VagrantConfigProvisioner.new(id, name.to_sym) + prov = VagrantConfigProvisioner.new(name, type.to_sym) @provisioners << prov end prov.preserve_order = !!options.delete(:preserve_order) if \ - options.has_key?(:preserve_order) - prov.run = options.delete(:run) if options.has_key?(:run) + options.key?(:preserve_order) + prov.run = options.delete(:run) if options.key?(:run) prov.add_config(options, &block) nil end @@ -321,6 +353,7 @@ module VagrantPlugins def finalize! # Defaults + @allowed_synced_folder_types = nil if @allowed_synced_folder_types == UNSET_VALUE @base_mac = nil if @base_mac == UNSET_VALUE @boot_timeout = 300 if @boot_timeout == UNSET_VALUE @box = nil if @box == UNSET_VALUE @@ -331,6 +364,7 @@ module VagrantPlugins @box_download_checksum_type = nil if @box_download_checksum_type == UNSET_VALUE @box_download_client_cert = nil if @box_download_client_cert == UNSET_VALUE @box_download_insecure = false if @box_download_insecure == UNSET_VALUE + @box_download_location_trusted = false if @box_download_location_trusted == UNSET_VALUE @box_url = nil if @box_url == UNSET_VALUE @box_version = nil if @box_version == UNSET_VALUE @communicator = nil if @communicator == UNSET_VALUE @@ -344,6 +378,10 @@ module VagrantPlugins @usable_port_range = (2200..2250) end + if @allowed_synced_folder_types + @allowed_synced_folder_types = Array(@allowed_synced_folder_types).map(&:to_sym) + end + # Make sure that the download checksum is a string and that # the type is a symbol @box_download_checksum = "" if !@box_download_checksum @@ -373,10 +411,15 @@ module VagrantPlugins host_ip: "127.0.0.1", id: "winrm", auto_correct: true - end - end - if !@__networks["forwarded_port-ssh"] + network :forwarded_port, + guest: 5986, + host: 55986, + host_ip: "127.0.0.1", + id: "winrm-ssl", + auto_correct: true + end + elsif !@__networks["forwarded_port-ssh"] network :forwarded_port, guest: 22, host: 2222, @@ -412,8 +455,20 @@ module VagrantPlugins config = config.merge(new_config) end rescue Exception => e + @logger.error("Vagrantfile load error: #{e.message}") + @logger.error(e.inspect) + @logger.error(e.message) + @logger.error(e.backtrace.join("\n")) + + line = "(unknown)" + if e.backtrace && e.backtrace[0] + line = e.backtrace[0].split(":")[1] + end + raise Vagrant::Errors::VagrantfileLoadError, path: "", + line: line, + exception_class: e.class, message: e.message end @@ -429,12 +484,7 @@ module VagrantPlugins p.run = p.run.to_sym if p.run end - # If we didn't share our current directory, then do it - # manually. - if !@__synced_folders["/vagrant"] - synced_folder(".", "/vagrant") - end - + current_dir_shared = false @__synced_folders.each do |id, options| if options[:nfs] options[:type] = :nfs @@ -444,6 +494,14 @@ module VagrantPlugins if options[:type] == :nfs && Vagrant::Util::Platform.windows? options.delete(:type) end + + if options[:hostpath] == '.' + current_dir_shared = true + end + end + + if !current_dir_shared && !@__synced_folders["/vagrant"] + synced_folder(".", "/vagrant") end # Flag that we finalized @@ -552,16 +610,18 @@ module VagrantPlugins guestpath = Pathname.new(options[:guestpath]) hostpath = Pathname.new(options[:hostpath]).expand_path(machine.env.root_path) - if guestpath.relative? && guestpath.to_s !~ /^\w+:/ - errors << I18n.t("vagrant.config.vm.shared_folder_guestpath_relative", - path: options[:guestpath]) - else - if used_guest_paths.include?(options[:guestpath]) - errors << I18n.t("vagrant.config.vm.shared_folder_guestpath_duplicate", + if guestpath.to_s != "" + if guestpath.relative? && guestpath.to_s !~ /^\w+:/ + errors << I18n.t("vagrant.config.vm.shared_folder_guestpath_relative", path: options[:guestpath]) - end + else + if used_guest_paths.include?(options[:guestpath]) + errors << I18n.t("vagrant.config.vm.shared_folder_guestpath_duplicate", + path: options[:guestpath]) + end - used_guest_paths.add(options[:guestpath]) + used_guest_paths.add(options[:guestpath]) + end end if !hostpath.directory? && !options[:create] @@ -569,7 +629,7 @@ module VagrantPlugins path: options[:hostpath]) end - if options[:type] == :nfs + if options[:type] == :nfs && !options[:nfs__quiet] if options[:owner] || options[:group] # Owner/group don't work with NFS errors << I18n.t("vagrant.config.vm.shared_folder_nfs_owner_group", @@ -649,8 +709,10 @@ module VagrantPlugins # Validate provisioners @provisioners.each do |vm_provisioner| if vm_provisioner.invalid? + name = vm_provisioner.name.to_s + name = vm_provisioner.type.to_s if name.empty? errors["vm"] << I18n.t("vagrant.config.vm.provisioner_not_found", - name: vm_provisioner.name) + name: name) next end @@ -673,6 +735,10 @@ module VagrantPlugins errors end + + def __providers + @__provider_order + end end end end diff --git a/plugins/kernel_v2/config/vm_provisioner.rb b/plugins/kernel_v2/config/vm_provisioner.rb index 5aa3945b7..aedafe9fe 100644 --- a/plugins/kernel_v2/config/vm_provisioner.rb +++ b/plugins/kernel_v2/config/vm_provisioner.rb @@ -4,16 +4,16 @@ module VagrantPlugins module Kernel_V2 # Represents a single configured provisioner for a VM. class VagrantConfigProvisioner - # Unique ID name for this provisioner + # Unique name for this provisioner # # @return [String] - attr_reader :id + attr_reader :name - # The name of the provisioner that should be registered + # The type of the provisioner that should be registered # as a plugin. # # @return [Symbol] - attr_reader :name + attr_reader :type # The configuration associated with the provisioner, if there is any. # @@ -31,30 +31,30 @@ module VagrantPlugins # @return [Boolean] attr_accessor :preserve_order - def initialize(id, name) + def initialize(name, type) @logger = Log4r::Logger.new("vagrant::config::vm::provisioner") @logger.debug("Provisioner defined: #{name}") @config = nil - @id = id @invalid = false @name = name @preserve_order = false @run = nil + @type = type # Attempt to find the provisioner... - if !Vagrant.plugin("2").manager.provisioners[name] - @logger.warn("Provisioner '#{name}' not found.") + if !Vagrant.plugin("2").manager.provisioners[type] + @logger.warn("Provisioner '#{type}' not found.") @invalid = true end # Attempt to find the configuration class for this provider # if it exists and load the configuration. @config_class = Vagrant.plugin("2").manager. - provisioner_configs[@name] + provisioner_configs[@type] if !@config_class @logger.info( - "Provisioner config for '#{@name}' not found. Ignoring config.") + "Provisioner config for '#{@type}' not found. Ignoring config.") @config_class = Vagrant::Config::V2::DummyConfig end end diff --git a/plugins/kernel_v2/plugin.rb b/plugins/kernel_v2/plugin.rb index 0904481df..27737854f 100644 --- a/plugins/kernel_v2/plugin.rb +++ b/plugins/kernel_v2/plugin.rb @@ -25,6 +25,11 @@ module VagrantPlugins PackageConfig end + config("push") do + require File.expand_path("../config/push", __FILE__) + PushConfig + end + config("vagrant") do require File.expand_path("../config/vagrant", __FILE__) VagrantConfig diff --git a/plugins/providers/docker/action.rb b/plugins/providers/docker/action.rb index 3189d6bec..bb376fb3d 100644 --- a/plugins/providers/docker/action.rb +++ b/plugins/providers/docker/action.rb @@ -20,14 +20,13 @@ module VagrantPlugins # container, configuring metadata, and booting. def self.action_up Vagrant::Action::Builder.new.tap do |b| - b.use ConfigValidate - b.use Call, IsState, :not_created do |env, b2| if env[:result] b2.use HandleBox end end + b.use ConfigValidate b.use HostMachine # Yeah, this is supposed to be here twice (once more above). This @@ -45,6 +44,12 @@ module VagrantPlugins end end + def self.action_package + lambda do |env| + raise Errors::PackageNotSupported + end + end + # This action just runs the provisioners on the machine. def self.action_provision Vagrant::Action::Builder.new.tap do |b| @@ -112,8 +117,11 @@ module VagrantPlugins b2.use Call, IsBuild do |env2, b3| if env2[:result] - b3.use EnvSet, force_confirm_destroy: true - b3.use action_destroy.flatten + b3.use EnvSet, force_halt: true + b3.use action_halt + b3.use HostMachineSyncFoldersDisable + b3.use Destroy + b3.use ProvisionerCleanup end end @@ -209,7 +217,7 @@ module VagrantPlugins def self.action_start Vagrant::Action::Builder.new.tap do |b| b.use Call, IsState, :running do |env, b2| - # If the container is running and we're doing a run, we're done + # If the container is running and we're not doing a run, we're done next if env[:result] && env[:machine_action] != :run_command if env[:machine_action] != :run_command @@ -225,7 +233,10 @@ module VagrantPlugins end b2.use Call, IsState, :not_created do |env2, b3| - if !env2[:result] + if env2[:result] + # First time making this thing, set to the "preparing" state + b3.use InitState + else b3.use EnvSet, host_machine_sync_folders: false end end @@ -235,12 +246,13 @@ module VagrantPlugins b2.use PrepareNFSValidIds b2.use SyncedFolderCleanup b2.use PrepareNFSSettings + b2.use Login b2.use Build if env[:machine_action] != :run_command # If the container is NOT created yet, then do some setup steps # necessary for creating it. - b2.use Call, IsState, :not_created do |env2, b3| + b2.use Call, IsState, :preparing do |env2, b3| if env2[:result] b3.use EnvSet, port_collision_repair: true b3.use HostMachinePortWarning @@ -248,6 +260,7 @@ module VagrantPlugins b3.use HandleForwardedPortCollisions b3.use SyncedFolders b3.use ForwardedPorts + b3.use Pull b3.use Create b3.use WaitForRunning else @@ -264,6 +277,7 @@ module VagrantPlugins end end else + # We're in a run command, so we do things a bit differently. b2.use SyncedFolders b2.use Create end @@ -271,6 +285,12 @@ module VagrantPlugins end end + def self.action_suspend + lambda do |env| + raise Errors::SuspendNotSupported + end + end + # The autoload farm action_root = Pathname.new(File.expand_path("../action", __FILE__)) autoload :Build, action_root.join("build") @@ -287,8 +307,11 @@ module VagrantPlugins autoload :HostMachineRequired, action_root.join("host_machine_required") autoload :HostMachineSyncFolders, action_root.join("host_machine_sync_folders") autoload :HostMachineSyncFoldersDisable, action_root.join("host_machine_sync_folders_disable") + autoload :InitState, action_root.join("init_state") autoload :IsBuild, action_root.join("is_build") autoload :IsHostMachineCreated, action_root.join("is_host_machine_created") + autoload :Login, action_root.join("login") + autoload :Pull, action_root.join("pull") autoload :PrepareSSH, action_root.join("prepare_ssh") autoload :Stop, action_root.join("stop") autoload :PrepareNFSValidIds, action_root.join("prepare_nfs_valid_ids") diff --git a/plugins/providers/docker/action/build.rb b/plugins/providers/docker/action/build.rb index 61cbcb8a6..0cd613045 100644 --- a/plugins/providers/docker/action/build.rb +++ b/plugins/providers/docker/action/build.rb @@ -1,9 +1,13 @@ require "log4r" +require "vagrant/util/ansi_escape_code_remover" + module VagrantPlugins module DockerProvider module Action class Build + include Vagrant::Util::ANSIEscapeCodeRemover + def initialize(app, env) @app = app @logger = Log4r::Logger.new("vagrant::docker::build") @@ -34,12 +38,28 @@ module VagrantPlugins # If we have no image or we're rebuilding, we rebuild if !image || env[:build_rebuild] # Build it - machine.ui.output(I18n.t("docker_provider.building")) + args = machine.provider_config.build_args.clone + if machine.provider_config.dockerfile + dockerfile = machine.provider_config.dockerfile + dockerfile_path = File.join(build_dir, dockerfile) + + args.push("--file=\"#{dockerfile_path}\"") + machine.ui.output( + I18n.t("docker_provider.building_named_dockerfile", + file: machine.provider_config.dockerfile)) + else + machine.ui.output(I18n.t("docker_provider.building")) + end + image = machine.provider.driver.build( build_dir, - extra_args: machine.provider_config.build_args, - ) - machine.ui.detail("Image: #{image}") + extra_args: args) do |type, data| + data = remove_ansi_escape_codes(data.chomp).chomp + env[:ui].detail(data) if data != "" + end + + # Output the final image + machine.ui.detail("\nImage: #{image}") # Store the image ID image_file.open("w") do |f| diff --git a/plugins/providers/docker/action/create.rb b/plugins/providers/docker/action/create.rb index e960f10b0..89034b117 100644 --- a/plugins/providers/docker/action/create.rb +++ b/plugins/providers/docker/action/create.rb @@ -96,10 +96,10 @@ module VagrantPlugins image = @env[:create_image] image ||= @provider_config.image - links = {} + links = [] @provider_config._links.each do |link| parts = link.split(":", 2) - links[parts[0]] = parts[1] + links << parts end { @@ -143,7 +143,9 @@ module VagrantPlugins result += mappings.values.map do |fp| protocol = "" protocol = "/udp" if fp[:protocol].to_s == "udp" - "#{fp[:host]}:#{fp[:guest]}#{protocol}" + host_ip = "" + host_ip = "#{fp[:host_ip]}:" if fp[:host_ip] + "#{host_ip}#{fp[:host]}:#{fp[:guest]}#{protocol}" end.compact result diff --git a/plugins/providers/docker/action/forwarded_ports.rb b/plugins/providers/docker/action/forwarded_ports.rb index db78c33ac..ed51b086d 100644 --- a/plugins/providers/docker/action/forwarded_ports.rb +++ b/plugins/providers/docker/action/forwarded_ports.rb @@ -8,9 +8,19 @@ module VagrantPlugins def call(env) env[:machine].provider_config.ports.each do |p| + host_ip = nil + protocol = "tcp" host, guest = p.split(":", 2) + if guest.include?(":") + host_ip = host + host, guest = guest.split(":", 2) + end + + guest, protocol = guest.split("/", 2) if guest.include?("/") env[:machine].config.vm.network "forwarded_port", - host: host.to_i, guest: guest.to_i + host: host.to_i, guest: guest.to_i, + host_ip: host_ip, + protocol: protocol end @app.call(env) diff --git a/plugins/providers/docker/action/host_machine.rb b/plugins/providers/docker/action/host_machine.rb index 2f38df5b9..56f4385bb 100644 --- a/plugins/providers/docker/action/host_machine.rb +++ b/plugins/providers/docker/action/host_machine.rb @@ -44,6 +44,10 @@ module VagrantPlugins proxy_ui.opts[:prefix_spaces] = true proxy_ui.opts[:target] = env[:machine].name.to_s + # Reload the machine so that if it was created while we didn't + # hold the lock, we'll see the updated state. + host_machine.reload + # See if the machine is ready already. If not, start it. if host_machine.communicate.ready? env[:machine].ui.detail(I18n.t("docker_provider.host_machine_ready")) diff --git a/plugins/providers/docker/action/host_machine_build_dir.rb b/plugins/providers/docker/action/host_machine_build_dir.rb index f43032631..4508e8c24 100644 --- a/plugins/providers/docker/action/host_machine_build_dir.rb +++ b/plugins/providers/docker/action/host_machine_build_dir.rb @@ -28,7 +28,7 @@ module VagrantPlugins # We're on a host VM, so we need to move our build dir to # that machine. We do this by putting the synced folder on # ourself and letting HostMachineSyncFolders handle it. - new_build_dir = "/mnt/docker_build_#{Digest::MD5.hexdigest(build_dir)}" + new_build_dir = "/var/lib/docker/docker_build_#{Digest::MD5.hexdigest(build_dir)}" options = { docker__ignore: true, docker__exact: true, diff --git a/plugins/providers/docker/action/host_machine_sync_folders.rb b/plugins/providers/docker/action/host_machine_sync_folders.rb index ac5857364..045b3bd04 100644 --- a/plugins/providers/docker/action/host_machine_sync_folders.rb +++ b/plugins/providers/docker/action/host_machine_sync_folders.rb @@ -22,7 +22,7 @@ module VagrantPlugins def call(env) return @app.call(env) if !env[:machine].provider.host_vm? - if !env.has_key?(:host_machine_sync_folders) + if !env.key?(:host_machine_sync_folders) env[:host_machine_sync_folders] = true end @@ -46,7 +46,6 @@ module VagrantPlugins def setup_synced_folders(host_machine, env) # Write the host machine SFID if we have one id_path = env[:machine].data_dir.join("host_machine_sfid") - host_sfid = nil if !id_path.file? host_sfid = SecureRandom.uuid id_path.open("w") do |f| @@ -116,7 +115,7 @@ module VagrantPlugins # Add this synced folder onto the new config if we haven't # already shared it before. - if !existing_ids.has_key?(id) + if !existing_ids.key?(id) # A bit of a hack for VirtualBox to mount our # folder as transient. This can be removed once # the VirtualBox synced folder mechanism is smarter. @@ -148,7 +147,6 @@ module VagrantPlugins if !env[:host_machine_sync_folders] @logger.info("Not syncing folders because container created.") - return end if !new_config.synced_folders.empty? diff --git a/plugins/providers/docker/action/init_state.rb b/plugins/providers/docker/action/init_state.rb new file mode 100644 index 000000000..cbb444547 --- /dev/null +++ b/plugins/providers/docker/action/init_state.rb @@ -0,0 +1,20 @@ +module VagrantPlugins + module DockerProvider + module Action + class InitState + def initialize(app, env) + @app = app + end + + def call(env) + # We set the ID of the machine to "preparing" so that we can use + # the data dir without it being deleted with the not_created state. + env[:machine].id = nil + env[:machine].id = "preparing" + + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/docker/action/login.rb b/plugins/providers/docker/action/login.rb new file mode 100644 index 000000000..a63c0fdc6 --- /dev/null +++ b/plugins/providers/docker/action/login.rb @@ -0,0 +1,39 @@ +require "log4r" + +module VagrantPlugins + module DockerProvider + module Action + class Login + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant::docker::login") + end + + def call(env) + config = env[:machine].provider_config + driver = env[:machine].provider.driver + + # If we don't have a password set, don't auth + return @app.call(env) if config.password == "" + + # Grab a host VM lock to do the login so that we only login + # once per container for the rest of this process. + env[:machine].provider.host_vm_lock do + # Login! + env[:ui].output(I18n.t("docker_provider.logging_in")) + driver.login( + config.email, config.username, + config.password, config.auth_server) + + # Continue, within the lock, so that the auth is protected + # from meddling. + @app.call(env) + + # Log out + driver.logout(config.auth_server) + end + end + end + end + end +end diff --git a/plugins/providers/docker/action/prepare_ssh.rb b/plugins/providers/docker/action/prepare_ssh.rb index 1a7298a66..450b139c6 100644 --- a/plugins/providers/docker/action/prepare_ssh.rb +++ b/plugins/providers/docker/action/prepare_ssh.rb @@ -19,14 +19,19 @@ module VagrantPlugins # Modify the SSH options for when we `vagrant ssh`... ssh_opts = env[:ssh_opts] || {} - # Build the command we'll execute within the host machine + # Build the command we'll execute within the Docker host machine: ssh_command = env[:machine].communicate.container_ssh_command if !Array(ssh_opts[:extra_args]).empty? ssh_command << " #{Array(ssh_opts[:extra_args]).join(" ")}" end + # Modify the SSH options for the original command: # Append "-t" to force a TTY allocation ssh_opts[:extra_args] = ["-t"] + # Enable Agent forwarding when requested for the target VM + if env[:machine].ssh_info[:forward_agent] + ssh_opts[:extra_args] << "-o ForwardAgent=yes" + end ssh_opts[:extra_args] << ssh_command # Set the opts diff --git a/plugins/providers/docker/action/pull.rb b/plugins/providers/docker/action/pull.rb new file mode 100644 index 000000000..20d620792 --- /dev/null +++ b/plugins/providers/docker/action/pull.rb @@ -0,0 +1,27 @@ +module VagrantPlugins + module DockerProvider + module Action + class Pull + def initialize(app, env) + @app = app + end + + def call(env) + @env = env + @machine = env[:machine] + @provider_config = @machine.provider_config + @driver = @machine.provider.driver + + # Skip pulling if the image is built + return @app.call(env) if @env[:create_image] + + image = @provider_config.image + env[:ui].output(I18n.t("docker_provider.pull", image: image)) + @driver.pull(image) + + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/docker/action/stop.rb b/plugins/providers/docker/action/stop.rb index f743c8b6c..2a30cf4a3 100644 --- a/plugins/providers/docker/action/stop.rb +++ b/plugins/providers/docker/action/stop.rb @@ -11,7 +11,7 @@ module VagrantPlugins driver = machine.provider.driver if driver.running?(machine.id) env[:ui].info I18n.t("docker_provider.messages.stopping") - driver.stop(machine.id) + driver.stop(machine.id, machine.provider_config.stop_timeout) end @app.call(env) end diff --git a/plugins/providers/docker/cap/proxy_machine.rb b/plugins/providers/docker/cap/proxy_machine.rb new file mode 100644 index 000000000..20dd487cc --- /dev/null +++ b/plugins/providers/docker/cap/proxy_machine.rb @@ -0,0 +1,12 @@ +module VagrantPlugins + module DockerProvider + module Cap + module ProxyMachine + def self.proxy_machine(machine) + return nil if !machine.provider.host_vm? + machine.provider.host_vm + end + end + end + end +end diff --git a/plugins/providers/docker/cap/public_address.rb b/plugins/providers/docker/cap/public_address.rb index 379b0452b..987cd668c 100644 --- a/plugins/providers/docker/cap/public_address.rb +++ b/plugins/providers/docker/cap/public_address.rb @@ -5,6 +5,14 @@ module VagrantPlugins def self.public_address(machine) return nil if machine.state.id != :running + # If we're using a host VM, then return the IP of that + # rather than of our own machine. + if machine.provider.host_vm? + host_machine = machine.provider.host_vm + return nil if !host_machine.provider.capability?(:public_address) + return host_machine.provider.capability(:public_address) + end + ssh_info = machine.ssh_info return nil if !ssh_info ssh_info[:host] diff --git a/plugins/providers/docker/command/logs.rb b/plugins/providers/docker/command/logs.rb index db119284a..a2abab4b2 100644 --- a/plugins/providers/docker/command/logs.rb +++ b/plugins/providers/docker/command/logs.rb @@ -43,7 +43,7 @@ module VagrantPlugins next end - state = machine.state + state = machine.state.id if state == :host_state_unknown machine.ui.output(I18n.t("docker_provider.logs_host_state_unknown")) next diff --git a/plugins/providers/docker/communicator.rb b/plugins/providers/docker/communicator.rb index 657789575..20b0df912 100644 --- a/plugins/providers/docker/communicator.rb +++ b/plugins/providers/docker/communicator.rb @@ -31,7 +31,17 @@ module VagrantPlugins end def download(from, to) - raise "NOT IMPLEMENTED YET" + # Same process as upload, but in reverse + + # First, we use `cat` to copy that file from the Docker container. + temp = "/tmp/docker_d#{Time.now.to_i}_#{rand(100000)}" + @host_vm.communicate.execute("#{container_ssh_command} 'cat #{from}' >#{temp}") + + # Then, we download this from the host VM. + @host_vm.communicate.download(temp, to) + + # Remove the temporary file + @host_vm.communicate.execute("rm -f #{temp}", error_check: false) end def execute(command, **opts, &block) @@ -137,18 +147,21 @@ module VagrantPlugins info[:port] ||= 22 # Make sure our private keys are synced over to the host VM - key_args = sync_private_keys(info).map do |path| + ssh_args = sync_private_keys(info).map do |path| "-i #{path}" - end.join(" ") + end + + # Use ad-hoc SSH options for the hop on the docker proxy + if info[:forward_agent] + ssh_args << "-o ForwardAgent=yes" + end + ssh_args.concat(["-o Compression=yes", + "-o ConnectTimeout=5", + "-o StrictHostKeyChecking=no", + "-o UserKnownHostsFile=/dev/null"]) # Build the SSH command - "ssh #{key_args} " + - "-o Compression=yes " + - "-o ConnectTimeout=5 " + - "-o StrictHostKeyChecking=no " + - "-o UserKnownHostsFile=/dev/null " + - "-p#{info[:port]} " + - "#{info[:username]}@#{info[:host]}" + "ssh #{info[:username]}@#{info[:host]} -p#{info[:port]} #{ssh_args.join(" ")}" end protected diff --git a/plugins/providers/docker/config.rb b/plugins/providers/docker/config.rb index 139c6955e..c06409a9f 100644 --- a/plugins/providers/docker/config.rb +++ b/plugins/providers/docker/config.rb @@ -17,6 +17,12 @@ module VagrantPlugins # @return [String] attr_accessor :build_dir + # An optional file name of a Dockerfile to be used when building + # the image. This requires Docker >1.5.0. + # + # @return [String] + attr_accessor :dockerfile + # Additional arguments to pass to `docker run` when creating # the container for the first time. This is an array of args. # @@ -61,6 +67,12 @@ module VagrantPlugins # @return [Boolean] attr_accessor :remains_running + # The time to wait before sending a SIGTERM to the container + # when it is stopped. + # + # @return [Integer] + attr_accessor :stop_timeout + # The name of the machine in the Vagrantfile set with # "vagrant_vagrantfile" that will be the docker host. Defaults # to "default" @@ -83,11 +95,43 @@ module VagrantPlugins # @return [String] attr_accessor :vagrant_vagrantfile + #-------------------------------------------------------------- + # Auth Settings + #-------------------------------------------------------------- + + # Server to authenticate to. If blank, will use the default + # Docker authentication endpoint (which is the Docker Hub at the + # time of this comment). + # + # @return [String] + attr_accessor :auth_server + + # Email for logging in to a remote Docker server. + # + # @return [String] + attr_accessor :email + + # Email for logging in to a remote Docker server. + # + # @return [String] + attr_accessor :username + + # Password for logging in to a remote Docker server. If this is + # not blank, then Vagrant will run `docker login` prior to any + # Docker runs. + # + # The presence of auth will also force the Docker environments to + # serialize on `up` so that different users/passwords don't overlap. + # + # @return [String] + attr_accessor :password + def initialize @build_args = [] @build_dir = UNSET_VALUE @cmd = UNSET_VALUE - @create_args = [] + @create_args = UNSET_VALUE + @dockerfile = UNSET_VALUE @env = {} @expose = [] @force_host_vm = UNSET_VALUE @@ -96,12 +140,18 @@ module VagrantPlugins @image = UNSET_VALUE @name = UNSET_VALUE @links = [] - @ports = [] + @ports = UNSET_VALUE @privileged = UNSET_VALUE @remains_running = UNSET_VALUE + @stop_timeout = UNSET_VALUE @volumes = [] @vagrant_machine = UNSET_VALUE @vagrant_vagrantfile = UNSET_VALUE + + @auth_server = UNSET_VALUE + @email = UNSET_VALUE + @username = UNSET_VALUE + @password = UNSET_VALUE end def link(name) @@ -143,16 +193,24 @@ module VagrantPlugins @build_dir = nil if @build_dir == UNSET_VALUE @cmd = [] if @cmd == UNSET_VALUE @create_args = [] if @create_args == UNSET_VALUE + @dockerfile = nil if @dockerfile == UNSET_VALUE @env ||= {} @force_host_vm = false if @force_host_vm == UNSET_VALUE @has_ssh = false if @has_ssh == UNSET_VALUE @image = nil if @image == UNSET_VALUE @name = nil if @name == UNSET_VALUE + @ports = [] if @ports == UNSET_VALUE @privileged = false if @privileged == UNSET_VALUE @remains_running = true if @remains_running == UNSET_VALUE + @stop_timeout = 1 if @stop_timeout == UNSET_VALUE @vagrant_machine = nil if @vagrant_machine == UNSET_VALUE @vagrant_vagrantfile = nil if @vagrant_vagrantfile == UNSET_VALUE + @auth_server = nil if @auth_server == UNSET_VALUE + @email = "" if @email == UNSET_VALUE + @username = "" if @username == UNSET_VALUE + @password = "" if @password == UNSET_VALUE + if @host_vm_build_dir_options == UNSET_VALUE @host_vm_build_dir_options = nil end @@ -176,11 +234,15 @@ module VagrantPlugins if @build_dir build_dir_pn = Pathname.new(@build_dir) - if !build_dir_pn.directory? || !build_dir_pn.join("Dockerfile").file? + if !build_dir_pn.directory? errors << I18n.t("docker_provider.errors.config.build_dir_invalid") end end + if !@create_args.is_a?(Array) + errors << I18n.t("docker_provider.errors.config.create_args_array") + end + @links.each do |link| parts = link.split(":") if parts.length != 2 || parts[0] == "" || parts[1] == "" diff --git a/plugins/providers/docker/driver.rb b/plugins/providers/docker/driver.rb index 5df5357e0..421369a96 100644 --- a/plugins/providers/docker/driver.rb +++ b/plugins/providers/docker/driver.rb @@ -14,19 +14,19 @@ module VagrantPlugins @executor = Executor::Local.new end - def build(dir, **opts) + def build(dir, **opts, &block) args = Array(opts[:extra_args]) args << dir - result = execute('docker', 'build', *args) - regexp = /Successfully built (.+)$/i - match = regexp.match(result) - if !match + result = execute('docker', 'build', *args, &block) + matches = result.scan(/Successfully built (.+)$/i) + if matches.empty? # This will cause a stack trace in Vagrant, but it is a bug # if this happens anyways. raise "UNKNOWN OUTPUT: #{result}" end - match[1] + # Return the last match, and the capture of it + matches[-1][0] end def create(params, **opts, &block) @@ -53,7 +53,7 @@ module VagrantPlugins run_cmd += params[:extra_args] if params[:extra_args] run_cmd += [image, cmd] - execute(*run_cmd.flatten, **opts, &block).chomp + execute(*run_cmd.flatten, **opts, &block).chomp.lines.last end def state(cid) @@ -86,6 +86,26 @@ module VagrantPlugins inspect_container(cid)['HostConfig']['Privileged'] end + def login(email, username, password, server) + cmd = %W(docker login) + cmd += ["-e", email] if email != "" + cmd += ["-u", username] if username != "" + cmd += ["-p", password] if password != "" + cmd << server if server && server != "" + + execute(*cmd.flatten) + end + + def logout(server) + cmd = %W(docker logout) + cmd << server if server && server != "" + execute(*cmd.flatten) + end + + def pull(image) + execute('docker', 'pull', image) + end + def start(cid) if !running?(cid) execute('docker', 'start', cid) @@ -95,9 +115,9 @@ module VagrantPlugins end end - def stop(cid) + def stop(cid, timeout) if running?(cid) - execute('docker', 'stop', '-t', '1', cid) + execute('docker', 'stop', '-t', timeout.to_s, cid) end end diff --git a/plugins/providers/docker/errors.rb b/plugins/providers/docker/errors.rb index 79e5a9767..4b3e71d64 100644 --- a/plugins/providers/docker/errors.rb +++ b/plugins/providers/docker/errors.rb @@ -25,6 +25,10 @@ module VagrantPlugins error_key(:docker_provider_nfs_without_privileged) end + class PackageNotSupported < DockerError + error_key(:package_not_supported) + end + class StateNotRunning < DockerError error_key(:state_not_running) end @@ -33,9 +37,17 @@ module VagrantPlugins error_key(:state_stopped) end + class SuspendNotSupported < DockerError + error_key(:suspend_not_supported) + end + class SyncedFolderNonDocker < DockerError error_key(:synced_folder_non_docker) end + + class VagrantfileNotFound < DockerError + error_key(:vagrantfile_not_found) + end end end end diff --git a/plugins/providers/docker/hostmachine/Vagrantfile b/plugins/providers/docker/hostmachine/Vagrantfile index 752ba888d..d9f1b9e71 100644 --- a/plugins/providers/docker/hostmachine/Vagrantfile +++ b/plugins/providers/docker/hostmachine/Vagrantfile @@ -1,21 +1,3 @@ Vagrant.configure("2") do |config| - config.vm.box = "mitchellh/boot2docker" - - config.vm.provider "virtualbox" do |v| - # On VirtualBox, we don't have guest additions or a functional vboxsf - # in TinyCore Linux, so tell Vagrant that so it can be smarter. - v.check_guest_additions = false - v.functional_vboxsf = false - end - - ["vmware_fusion", "vmware_workstation"].each do |vmware| - config.vm.provider vmware do |v| - if v.respond_to?(:functional_hgfs=) - v.functional_hgfs = false - end - end - end - - # b2d doesn't support NFS - config.nfs.functional = false + config.vm.box = "hashicorp/boot2docker" end diff --git a/plugins/providers/docker/plugin.rb b/plugins/providers/docker/plugin.rb index 1558aa51d..36ab08687 100644 --- a/plugins/providers/docker/plugin.rb +++ b/plugins/providers/docker/plugin.rb @@ -56,6 +56,11 @@ module VagrantPlugins Cap::PublicAddress end + provider_capability("docker", "proxy_machine") do + require_relative "cap/proxy_machine" + Cap::ProxyMachine + end + protected def self.init! diff --git a/plugins/providers/docker/provider.rb b/plugins/providers/docker/provider.rb index 28d5b9967..f30f32d41 100644 --- a/plugins/providers/docker/provider.rb +++ b/plugins/providers/docker/provider.rb @@ -73,6 +73,10 @@ module VagrantPlugins host_machine_name = :default end + # Expand it so that the home directories and so on get processed + # properly. + vf_path = File.expand_path(vf_path, @machine.env.root_path) + vf_file = File.basename(vf_path) vf_path = File.dirname(vf_path) @@ -85,6 +89,10 @@ module VagrantPlugins vagrantfile_name: vf_file, ) + # If there is no root path, then the Vagrantfile wasn't found + # and it is an error... + raise Errors::VagrantfileNotFound if !host_env.root_path + host_env.machine( host_machine_name, host_env.default_provider( @@ -145,13 +153,24 @@ module VagrantPlugins def state state_id = nil state_id = :not_created if !@machine.id - state_id = :host_state_unknown if !state_id && \ - host_vm? && !host_vm.communicate.ready? + + begin + state_id = :host_state_unknown if !state_id && \ + host_vm? && !host_vm.communicate.ready? + rescue Errors::VagrantfileNotFound + state_id = :host_state_unknown + end + state_id = :not_created if !state_id && \ (!@machine.id || !driver.created?(@machine.id)) state_id = driver.state(@machine.id) if @machine.id && !state_id state_id = :unknown if !state_id + # This is a special pseudo-state so that we don't set the + # NOT_CREATED_ID while we're setting up the machine. This avoids + # clearing the data dir. + state_id = :preparing if @machine.id == "preparing" + short = state_id.to_s.gsub("_", " ") long = I18n.t("docker_provider.status.#{state_id}") diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb index 49e2be33f..64fbec180 100644 --- a/plugins/providers/hyperv/action.rb +++ b/plugins/providers/hyperv/action.rb @@ -116,6 +116,7 @@ module VagrantPlugins end b2.use Provision + b2.use NetSetVLan b2.use StartInstance b2.use WaitForIPAddress b2.use WaitForCommunicator, [:running] @@ -216,6 +217,7 @@ module VagrantPlugins autoload :StopInstance, action_root.join('stop_instance') autoload :SuspendVM, action_root.join("suspend_vm") autoload :WaitForIPAddress, action_root.join("wait_for_ip_address") + autoload :NetSetVLan, action_root.join("net_set_vlan") autoload :MessageWillNotDestroy, action_root.join("message_will_not_destroy") end end diff --git a/plugins/providers/hyperv/action/import.rb b/plugins/providers/hyperv/action/import.rb index 0687503af..0b617ad54 100644 --- a/plugins/providers/hyperv/action/import.rb +++ b/plugins/providers/hyperv/action/import.rb @@ -14,6 +14,15 @@ module VagrantPlugins def call(env) vm_dir = env[:machine].box.directory.join("Virtual Machines") hd_dir = env[:machine].box.directory.join("Virtual Hard Disks") + memory = env[:machine].provider_config.memory + maxmemory = env[:machine].provider_config.maxmemory + cpus = env[:machine].provider_config.cpus + vmname = env[:machine].provider_config.vmname + + env[:ui].output("Configured Dynamical memory allocation, maxmemory is #{maxmemory}") if maxmemory + env[:ui].output("Configured startup memory is #{memory}") if memory + env[:ui].output("Configured cpus number is #{cpus}") if cpus + env[:ui].output("Configured vmname is #{vmname}") if vmname if !vm_dir.directory? || !hd_dir.directory? raise Errors::BoxInvalid @@ -46,23 +55,39 @@ module VagrantPlugins switches = env[:machine].provider.driver.execute("get_switches.ps1", {}) raise Errors::NoSwitches if switches.empty? - switch = switches[0]["Name"] - if switches.length > 1 - env[:ui].detail(I18n.t("vagrant_hyperv.choose_switch") + "\n ") - switches.each_index do |i| - switch = switches[i] - env[:ui].detail("#{i+1}) #{switch["Name"]}") - end - env[:ui].detail(" ") + switch = nil + env[:machine].config.vm.networks.each do |type, opts| + next if type != :public_network && type != :private_network - switch = nil - while !switch - switch = env[:ui].ask("What switch would you like to use? ") - next if !switch - switch = switch.to_i - 1 - switch = nil if switch < 0 || switch >= switches.length + switchToFind = opts[:bridge] + + if switchToFind + puts "Looking for switch with name: #{switchToFind}" + switch = switches.find { |s| s["Name"].downcase == switchToFind.downcase }["Name"] + puts "Found switch: #{switch}" + end + end + + if switch.nil? + if switches.length > 1 + env[:ui].detail(I18n.t("vagrant_hyperv.choose_switch") + "\n ") + switches.each_index do |i| + switch = switches[i] + env[:ui].detail("#{i+1}) #{switch["Name"]}") + end + env[:ui].detail(" ") + + switch = nil + while !switch + switch = env[:ui].ask("What switch would you like to use? ") + next if !switch + switch = switch.to_i - 1 + switch = nil if switch < 0 || switch >= switches.length + end + switch = switches[switch]["Name"] + else + switch = switches[0]["Name"] end - switch = switches[switch]["Name"] end env[:ui].detail("Cloning virtual hard drive...") @@ -78,6 +103,10 @@ module VagrantPlugins image_path: image_path.to_s.gsub("/", "\\") } options[:switchname] = switch if switch + options[:memory] = memory if memory + options[:maxmemory] = maxmemory if maxmemory + options[:cpus] = cpus if cpus + options[:vmname] = vmname if vmname env[:ui].detail("Creating and registering the VM...") server = env[:machine].provider.driver.import(options) diff --git a/plugins/providers/hyperv/action/net_set_vlan.rb b/plugins/providers/hyperv/action/net_set_vlan.rb new file mode 100644 index 000000000..c65f5a7cb --- /dev/null +++ b/plugins/providers/hyperv/action/net_set_vlan.rb @@ -0,0 +1,20 @@ +module VagrantPlugins + module HyperV + module Action + class NetSetVLan + def initialize(app, env) + @app = app + end + + def call(env) + vlan_id = env[:machine].provider_config.vlan_id + if vlan_id + env[:ui].info("[Settings] [Network Adapter] Setting Vlan ID to: #{vlan_id}") + env[:machine].provider.driver.net_set_vlan(vlan_id) + end + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/config.rb b/plugins/providers/hyperv/config.rb index ad69e3059..fe247a242 100644 --- a/plugins/providers/hyperv/config.rb +++ b/plugins/providers/hyperv/config.rb @@ -3,20 +3,32 @@ require "vagrant" module VagrantPlugins module HyperV class Config < Vagrant.plugin("2", :config) - # The timeout to wait for an IP address when booting the machine, - # in seconds. - # - # @return [Integer] - attr_accessor :ip_address_timeout + + attr_accessor :ip_address_timeout # Time to wait for an IP address when booting, in seconds @return [Integer] + attr_accessor :memory # Memory size in mb @return [Integer] + attr_accessor :maxmemory # Maximal memory size in mb enables dynamical memory allocation @return [Integer] + attr_accessor :cpus # Number of cpu's @return [Integer] + attr_accessor :vmname # Name that will be shoen in Hyperv Manager @return [String] + attr_accessor :vlan_id # VLAN ID for network interface for the virtual machine. @return [Integer] def initialize @ip_address_timeout = UNSET_VALUE + @memory = UNSET_VALUE + @maxmemory = UNSET_VALUE + @cpus = UNSET_VALUE + @vmname = UNSET_VALUE + @vlan_id = UNSET_VALUE end def finalize! if @ip_address_timeout == UNSET_VALUE @ip_address_timeout = 120 end + @memory = nil if @memory == UNSET_VALUE + @maxmemory = nil if @maxmemory == UNSET_VALUE + @cpus = nil if @cpus == UNSET_VALUE + @vmname = nil if @vmname == UNSET_VALUE + @vlan_id = nil if @vlan_id == UNSET_VALUE end def validate(machine) diff --git a/plugins/providers/hyperv/driver.rb b/plugins/providers/hyperv/driver.rb index 9f11881e6..db99d5dfe 100644 --- a/plugins/providers/hyperv/driver.rb +++ b/plugins/providers/hyperv/driver.rb @@ -77,6 +77,10 @@ module VagrantPlugins execute('import_vm.ps1', options) end + def net_set_vlan(vlan_id) + execute("set_network_vlan.ps1", { VmId: vm_id, VlanId: vlan_id }) + end + protected def execute_powershell(path, options, &block) diff --git a/plugins/providers/hyperv/scripts/import_vm.ps1 b/plugins/providers/hyperv/scripts/import_vm.ps1 index 0b1d74a7e..3e8664eb2 100644 --- a/plugins/providers/hyperv/scripts/import_vm.ps1 +++ b/plugins/providers/hyperv/scripts/import_vm.ps1 @@ -4,7 +4,11 @@ Param( [Parameter(Mandatory=$true)] [string]$image_path, - [string]$switchname=$null + [string]$switchname=$null, + [string]$memory=$null, + [string]$maxmemory=$null, + [string]$cpus=$null, + [string]$vmname=$null ) # Include the following modules @@ -13,10 +17,22 @@ $Dir = Split-Path $script:MyInvocation.MyCommand.Path [xml]$vmconfig = Get-Content -Path $vm_xml_config -$vm_name = $vmconfig.configuration.properties.name.'#text' -$processors = $vmconfig.configuration.settings.processors.count.'#text' $generation = [int]($vmconfig.configuration.properties.subtype.'#text')+1 +if (!$vmname) { + # Get the name of the vm + $vm_name = $vmconfig.configuration.properties.name.'#text' +}else { + $vm_name = $vmname +} + +if (!$cpus) { + # Get the name of the vm + $processors = $vmconfig.configuration.settings.processors.count.'#text' +}else { + $processors = $cpus +} + function GetUniqueName($name) { Get-VM | ForEach-Object -Process { if ($name -eq $_.Name) { @@ -31,18 +47,34 @@ do { $vm_name = GetUniqueName $name } while ($vm_name -ne $name) -$memory = (Select-Xml -xml $vmconfig -XPath "//memory").node.Bank -if ($memory.dynamic_memory_enabled."#text" -eq "True") { - $dynamicmemory = $True +if (!$memory) { + $xmlmemory = (Select-Xml -xml $vmconfig -XPath "//memory").node.Bank + if ($xmlmemory.dynamic_memory_enabled."#text" -eq "True") { + $dynamicmemory = $True + } + else { + $dynamicmemory = $False + } + # Memory values need to be in bytes + $MemoryMaximumBytes = ($xmlmemory.limit."#text" -as [int]) * 1MB + $MemoryStartupBytes = ($xmlmemory.size."#text" -as [int]) * 1MB + $MemoryMinimumBytes = ($xmlmemory.reservation."#text" -as [int]) * 1MB } else { - $dynamicmemory = $False + if (!$maxmemory){ + $dynamicmemory = $False + $MemoryMaximumBytes = ($memory -as [int]) * 1MB + $MemoryStartupBytes = ($memory -as [int]) * 1MB + $MemoryMinimumBytes = ($memory -as [int]) * 1MB + } + else { + $dynamicmemory = $True + $MemoryMaximumBytes = ($maxmemory -as [int]) * 1MB + $MemoryStartupBytes = ($memory -as [int]) * 1MB + $MemoryMinimumBytes = ($memory -as [int]) * 1MB + } } -# Memory values need to be in bytes -$MemoryMaximumBytes = ($memory.limit."#text" -as [int]) * 1MB -$MemoryStartupBytes = ($memory.size."#text" -as [int]) * 1MB -$MemoryMinimumBytes = ($memory.reservation."#text" -as [int]) * 1MB if (!$switchname) { # Get the name of the virtual switch @@ -58,6 +90,9 @@ Switch ((Select-Xml -xml $vmconfig -XPath "//boot").node.device0."#text") { "Default" { $bootdevice = "IDE" } } #switch +# Determine secure boot options +$secure_boot_enabled = (Select-Xml -xml $vmconfig -XPath "//secure_boot_enabled").Node."#text" + # Define a hash map of parameter values for New-VM $vm_params = @{ @@ -101,6 +136,16 @@ $vm | Set-VM @more_vm_params -Passthru # Add drives to the virtual machine $controllers = Select-Xml -xml $vmconfig -xpath "//*[starts-with(name(.),'controller')]" +# Only set EFI secure boot for Gen 2 machines, not gen 1 +if ($generation -ne 1) { + # Set EFI secure boot + if ($secure_boot_enabled -eq "True") { + Set-VMFirmware -VM $vm -EnableSecureBoot On + } else { + Set-VMFirmware -VM $vm -EnableSecureBoot Off + } +} + # A regular expression pattern to pull the number from controllers [regex]$rx="\d" diff --git a/plugins/providers/hyperv/scripts/set_network_vlan.ps1 b/plugins/providers/hyperv/scripts/set_network_vlan.ps1 new file mode 100644 index 000000000..a2b271b91 --- /dev/null +++ b/plugins/providers/hyperv/scripts/set_network_vlan.ps1 @@ -0,0 +1,18 @@ +param ( + [string]$VmId = $(throw "-VmId is required."), + [int]$VlanId = $(throw "-VlanId ") + ) + +# Include the following modules +$presentDir = Split-Path -parent $PSCommandPath +$modules = @() +$modules += $presentDir + "\utils\write_messages.ps1" +forEach ($module in $modules) { . $module } + +try { + $vm = Get-VM -Id $VmId -ErrorAction "stop" + Set-VMNetworkAdapterVlan $vm -Access -Vlanid $VlanId +} +catch { + Write-Error-Message "Failed to set VM's Vlan ID $_" +} diff --git a/plugins/providers/hyperv/scripts/start_vm.ps1 b/plugins/providers/hyperv/scripts/start_vm.ps1 index 937ce75ed..54025c9a4 100644 --- a/plugins/providers/hyperv/scripts/start_vm.ps1 +++ b/plugins/providers/hyperv/scripts/start_vm.ps1 @@ -10,7 +10,7 @@ forEach ($module in $modules) { . $module } try { $vm = Get-VM -Id $VmId -ErrorAction "stop" - Start-VM $vm + Start-VM $vm -ErrorAction "stop" $state = $vm.state $status = $vm.status $name = $vm.name @@ -24,4 +24,4 @@ try { } catch { Write-Error-Message "Failed to start a VM $_" -} +} \ No newline at end of file diff --git a/plugins/providers/virtualbox/action.rb b/plugins/providers/virtualbox/action.rb index b0051ce3d..96bc2a8f1 100644 --- a/plugins/providers/virtualbox/action.rb +++ b/plugins/providers/virtualbox/action.rb @@ -294,6 +294,16 @@ module VagrantPlugins end end + # This is the action that is called to sync folders to a running + # machine without a reboot. + def self.action_sync_folders + Vagrant::Action::Builder.new.tap do |b| + b.use PrepareNFSValidIds + b.use SyncedFolders + b.use PrepareNFSSettings + end + end + # This action brings the machine up from nothing, including importing # the box, configuring metadata, and booting. def self.action_up diff --git a/plugins/providers/virtualbox/action/network.rb b/plugins/providers/virtualbox/action/network.rb index 0b006360d..3697cdfef 100644 --- a/plugins/providers/virtualbox/action/network.rb +++ b/plugins/providers/virtualbox/action/network.rb @@ -157,12 +157,16 @@ module VagrantPlugins @logger.debug("Bridge was directly specified in config, searching for: #{config[:bridge]}") # Search for a matching bridged interface - bridgedifs.each do |interface| - if interface[:name].downcase == config[:bridge].downcase - @logger.debug("Specific bridge found as configured in the Vagrantfile. Using it.") - chosen_bridge = interface[:name] - break + Array(config[:bridge]).each do |bridge| + bridge = bridge.downcase if bridge.respond_to?(:downcase) + bridgedifs.each do |interface| + if bridge === interface[:name].downcase + @logger.debug("Specific bridge found as configured in the Vagrantfile. Using it.") + chosen_bridge = interface[:name] + break + end end + break if chosen_bridge end # If one wasn't found, then we notify the user here. @@ -184,12 +188,15 @@ module VagrantPlugins else # More than one bridgable interface requires a user decision, so # show options to choose from. - @env[:ui].info I18n.t("vagrant.actions.vm.bridged_networking.available", - prefix: false) + @env[:ui].info I18n.t( + "vagrant.actions.vm.bridged_networking.available", + prefix: false) bridgedifs.each_index do |index| interface = bridgedifs[index] @env[:ui].info("#{index + 1}) #{interface[:name]}", prefix: false) end + @env[:ui].info(I18n.t( + "vagrant.actions.vm.bridged_networking.choice_help")+"\n") # The range of valid choices valid = Range.new(1, bridgedifs.length) @@ -197,7 +204,8 @@ module VagrantPlugins # The choice that the user has chosen as the bridging interface choice = nil while !valid.include?(choice) - choice = @env[:ui].ask("What interface should the network bridge to? ") + choice = @env[:ui].ask( + "Which interface should the network bridge to? ") choice = choice.to_i end @@ -281,16 +289,16 @@ module VagrantPlugins # with the final octet + 2. So "172.28.0.0" turns into "172.28.0.2" dhcp_ip = ip_parts.dup dhcp_ip[3] += 2 - dhcp_options[:dhcp_ip] ||= dhcp_ip.join(".") + dhcp_options[:dhcp_ip] = options[:dhcp_ip] || dhcp_ip.join(".") # Calculate the lower and upper bound for the DHCP server dhcp_lower = ip_parts.dup dhcp_lower[3] += 3 - dhcp_options[:dhcp_lower] ||= dhcp_lower.join(".") + dhcp_options[:dhcp_lower] = options[:dhcp_lower] || dhcp_lower.join(".") dhcp_upper = ip_parts.dup dhcp_upper[3] = 254 - dhcp_options[:dhcp_upper] ||= dhcp_upper.join(".") + dhcp_options[:dhcp_upper] = options[:dhcp_upper] || dhcp_upper.join(".") end return { @@ -298,6 +306,7 @@ module VagrantPlugins auto_config: options[:auto_config], ip: options[:ip], mac: options[:mac], + name: options[:name], netmask: options[:netmask], nic_type: options[:nic_type], type: options[:type] @@ -323,21 +332,7 @@ module VagrantPlugins end if config[:type] == :dhcp - # Check that if there is a DHCP server attached on our interface, - # then it is identical. Otherwise, we can't set it. - if interface[:dhcp] - valid = interface[:dhcp][:ip] == config[:dhcp_ip] && - interface[:dhcp][:lower] == config[:dhcp_lower] && - interface[:dhcp][:upper] == config[:dhcp_upper] - - raise Vagrant::Errors::NetworkDHCPAlreadyAttached if !valid - - @logger.debug("DHCP server already properly configured") - else - # Configure the DHCP server for the network. - @logger.debug("Creating a DHCP server...") - @env[:machine].provider.driver.create_dhcp_server(interface[:name], config) - end + create_dhcp_server_if_necessary(interface, config) end return { @@ -467,6 +462,70 @@ module VagrantPlugins nil end + + #----------------------------------------------------------------- + # DHCP Server Helper Functions + #----------------------------------------------------------------- + + DEFAULT_DHCP_SERVER_FROM_VBOX_INSTALL = { + network_name: 'HostInterfaceNetworking-vboxnet0', + network: 'vboxnet0', + ip: '192.168.56.100', + netmask: '255.255.255.0', + lower: '192.168.56.101', + upper: '192.168.56.254' + }.freeze + + # + # When a host-only network of type: :dhcp is configured, + # this handles the potential creation of a vbox dhcpserver to manage + # it. + # + # @param [Hash] interface hash as returned from read_host_only_interfaces + # @param [Hash] config hash as returned from hostonly_config + def create_dhcp_server_if_necessary(interface, config) + existing_dhcp_server = find_matching_dhcp_server(interface) + + if existing_dhcp_server + if dhcp_server_matches_config?(existing_dhcp_server, config) + @logger.debug("DHCP server already properly configured") + return + elsif existing_dhcp_server == DEFAULT_DHCP_SERVER_FROM_VBOX_INSTALL + @env[:ui].info I18n.t("vagrant.actions.vm.network.cleanup_vbox_default_dhcp") + @env[:machine].provider.driver.remove_dhcp_server(existing_dhcp_server[:network_name]) + else + # We have an invalid DHCP server that we're not able to + # automatically clean up, so we need to give up and tell the user + # to sort out their own vbox dhcpservers and hostonlyifs + raise Vagrant::Errors::NetworkDHCPAlreadyAttached + end + end + + @logger.debug("Creating a DHCP server...") + @env[:machine].provider.driver.create_dhcp_server(interface[:name], config) + end + + # Detect when an existing DHCP server matches precisely the + # requested config for a hostonly interface. + # + # @param [Hash] dhcp_server as found by read_dhcp_servers + # @param [Hash] config as returned from hostonly_config + # @return [Boolean] + def dhcp_server_matches_config?(dhcp_server, config) + dhcp_server[:ip] == config[:dhcp_ip] && + dhcp_server[:lower] == config[:dhcp_lower] && + dhcp_server[:upper] == config[:dhcp_upper] + end + + # Returns the existing dhcp server, if any, that is attached to the + # specified interface. + # + # @return [Hash] dhcp_server or nil if not found + def find_matching_dhcp_server(interface) + @env[:machine].provider.driver.read_dhcp_servers.detect do |dhcp_server| + interface[:name] && interface[:name] == dhcp_server[:network] + end + end end end end diff --git a/plugins/providers/virtualbox/action/prepare_nfs_settings.rb b/plugins/providers/virtualbox/action/prepare_nfs_settings.rb index b88cee99f..d3edf3e93 100644 --- a/plugins/providers/virtualbox/action/prepare_nfs_settings.rb +++ b/plugins/providers/virtualbox/action/prepare_nfs_settings.rb @@ -1,7 +1,10 @@ +require "vagrant/action/builtin/mixin_synced_folders" + module VagrantPlugins module ProviderVirtualBox module Action class PrepareNFSSettings + include Vagrant::Action::Builtin::MixinSyncedFolders include Vagrant::Util::Retryable def initialize(app, env) @@ -14,19 +17,19 @@ module VagrantPlugins @app.call(env) - if using_nfs? + opts = { + cached: !!env[:synced_folders_cached], + config: env[:synced_folders_config], + disable_usable_check: !!env[:test], + } + folders = synced_folders(env[:machine], **opts) + + if folders.key?(:nfs) @logger.info("Using NFS, preparing NFS settings by reading host IP and machine IP") add_ips_to_env!(env) end end - # We're using NFS if we have any synced folder with NFS configured. If - # we are not using NFS we don't need to do the extra work to - # populate these fields in the environment. - def using_nfs? - @machine.config.vm.synced_folders.any? { |_, opts| opts[:type] == :nfs } - end - # Extracts the proper host and guest IPs for NFS mounts and stores them # in the environment for the SyncedFolder action to use them in # mounting. diff --git a/plugins/providers/virtualbox/action/set_name.rb b/plugins/providers/virtualbox/action/set_name.rb index 3cbcf131a..0e80f4591 100644 --- a/plugins/providers/virtualbox/action/set_name.rb +++ b/plugins/providers/virtualbox/action/set_name.rb @@ -32,9 +32,9 @@ module VagrantPlugins # Verify the name is not taken vms = env[:machine].provider.driver.read_vms raise Vagrant::Errors::VMNameExists, name: name if \ - vms.has_key?(name) && vms[name] != env[:machine].id + vms.key?(name) && vms[name] != env[:machine].id - if vms.has_key?(name) + if vms.key?(name) @logger.info("Not setting the name because our name is already set.") else env[:ui].info(I18n.t( diff --git a/plugins/providers/virtualbox/config.rb b/plugins/providers/virtualbox/config.rb index 7a0e08d08..55b8356aa 100644 --- a/plugins/providers/virtualbox/config.rb +++ b/plugins/providers/virtualbox/config.rb @@ -153,7 +153,7 @@ module VagrantPlugins def validate(machine) errors = _detected_errors - valid_events = ["pre-import", "pre-boot", "post-boot"] + valid_events = ["pre-import", "pre-boot", "post-boot", "post-comm"] @customizations.each do |event, _| if !valid_events.include?(event) errors << I18n.t( diff --git a/plugins/providers/virtualbox/driver/base.rb b/plugins/providers/virtualbox/driver/base.rb index 2cb1a7093..112e97d5b 100644 --- a/plugins/providers/virtualbox/driver/base.rb +++ b/plugins/providers/virtualbox/driver/base.rb @@ -30,8 +30,8 @@ module VagrantPlugins # On Windows, we use the VBOX_INSTALL_PATH environmental # variable to find VBoxManage. - if ENV.has_key?("VBOX_INSTALL_PATH") || - ENV.has_key?("VBOX_MSI_INSTALL_PATH") + if ENV.key?("VBOX_INSTALL_PATH") || + ENV.key?("VBOX_MSI_INSTALL_PATH") # Get the path. path = ENV["VBOX_INSTALL_PATH"] || ENV["VBOX_MSI_INSTALL_PATH"] @logger.debug("VBOX_INSTALL_PATH value: #{path}") @@ -180,6 +180,22 @@ module VagrantPlugins def read_bridged_interfaces end + # Returns a list of configured DHCP servers + # + # Each DHCP server is represented as a Hash with the following details: + # + # { + # :network => String, # name of the associated network interface as + # # parsed from the NetworkName, e.g. "vboxnet0" + # :ip => String, # IP address of the DHCP server, e.g. "172.28.128.2" + # :lower => String, # lower IP address of the DHCP lease range, e.g. "172.28.128.3" + # :upper => String, # upper IP address of the DHCP lease range, e.g. "172.28.128.254" + # } + # + # @return [Array] See comment above for details + def read_dhcp_servers + end + # Returns the guest additions version that is installed on this VM. # # @return [String] @@ -196,7 +212,16 @@ module VagrantPlugins # Returns a list of available host only interfaces. # - # @return [Hash] + # Each interface is represented as a Hash with the following details: + # + # { + # :name => String, # interface name, e.g. "vboxnet0" + # :ip => String, # IP address of the interface, e.g. "172.28.128.1" + # :netmask => String, # netmask associated with the interface, e.g. "255.255.255.0" + # :status => String, # status of the interface, e.g. "Up", "Down" + # } + # + # @return [Array] See comment above for details def read_host_only_interfaces end @@ -238,6 +263,13 @@ module VagrantPlugins def read_vms end + # Removes the DHCP server identified by the provided network name. + # + # @param [String] network_name The the full network name associated + # with the DHCP server to be removed, e.g. "HostInterfaceNetworking-vboxnet0" + def remove_dhcp_server(network_name) + end + # Sets the MAC address of the first network adapter. # # @param [String] mac MAC address without any spaces/hyphens. @@ -371,6 +403,9 @@ module VagrantPlugins Vagrant::Util::Busy.busy(int_callback) do Vagrant::Util::Subprocess.execute(@vboxmanage_path, *command, &block) end + rescue Vagrant::Util::Subprocess::LaunchError => e + raise Vagrant::Errors::VBoxManageLaunchError, + message: e.to_s end end end diff --git a/plugins/providers/virtualbox/driver/meta.rb b/plugins/providers/virtualbox/driver/meta.rb index 5f0700c32..80756f9d9 100644 --- a/plugins/providers/virtualbox/driver/meta.rb +++ b/plugins/providers/virtualbox/driver/meta.rb @@ -2,6 +2,8 @@ require "forwardable" require "log4r" +require "vagrant/util/retryable" + require File.expand_path("../base", __FILE__) module VagrantPlugins @@ -21,6 +23,8 @@ module VagrantPlugins # The version of virtualbox that is running. attr_reader :version + include Vagrant::Util::Retryable + def initialize(uuid=nil) # Setup the base super() @@ -45,7 +49,8 @@ module VagrantPlugins "4.0" => Version_4_0, "4.1" => Version_4_1, "4.2" => Version_4_2, - "4.3" => Version_4_3 + "4.3" => Version_4_3, + "5.0" => Version_5_0, } if @version.start_with?("4.2.14") @@ -94,6 +99,7 @@ module VagrantPlugins :import, :read_forwarded_ports, :read_bridged_interfaces, + :read_dhcp_servers, :read_guest_additions_version, :read_guest_ip, :read_guest_property, @@ -105,6 +111,7 @@ module VagrantPlugins :read_state, :read_used_ports, :read_vms, + :remove_dhcp_server, :resume, :set_mac_address, :set_name, @@ -132,16 +139,25 @@ module VagrantPlugins # Note: We split this into multiple lines because apparently "".split("_") # is [], so we have to check for an empty array in between. - output = execute("--version") - if output =~ /vboxdrv kernel module is not loaded/ || - output =~ /VirtualBox kernel modules are not loaded/i - raise Vagrant::Errors::VirtualBoxKernelModuleNotLoaded - elsif output =~ /Please install/ - # Check for installation incomplete warnings, for example: - # "WARNING: The character device /dev/vboxdrv does not - # exist. Please install the virtualbox-ose-dkms package and - # the appropriate headers, most likely linux-headers-generic." - raise Vagrant::Errors::VirtualBoxInstallIncomplete + output = "" + retryable(on: Vagrant::Errors::VirtualBoxVersionEmpty, tries: 3, sleep: 1) do + output = execute("--version") + if output =~ /vboxdrv kernel module is not loaded/ || + output =~ /VirtualBox kernel modules are not loaded/i + raise Vagrant::Errors::VirtualBoxKernelModuleNotLoaded + elsif output =~ /Please install/ + # Check for installation incomplete warnings, for example: + # "WARNING: The character device /dev/vboxdrv does not + # exist. Please install the virtualbox-ose-dkms package and + # the appropriate headers, most likely linux-headers-generic." + raise Vagrant::Errors::VirtualBoxInstallIncomplete + elsif output.chomp == "" + # This seems to happen on Windows for uncertain reasons. + # Raise an error otherwise the error is that they have an + # incompatible version of VirtualBox which isn't true. + raise Vagrant::Errors::VirtualBoxVersionEmpty, + vboxmanage: @vboxmanage_path.to_s + end end parts = output.split("_") diff --git a/plugins/providers/virtualbox/driver/version_4_0.rb b/plugins/providers/virtualbox/driver/version_4_0.rb index 670adfd0a..53361bbbb 100644 --- a/plugins/providers/virtualbox/driver/version_4_0.rb +++ b/plugins/providers/virtualbox/driver/version_4_0.rb @@ -255,6 +255,29 @@ module VagrantPlugins end end + def read_dhcp_servers + execute("list", "dhcpservers", retryable: true).split("\n\n").collect do |block| + info = {} + + block.split("\n").each do |line| + if network = line[/^NetworkName:\s+HostInterfaceNetworking-(.+?)$/, 1] + info[:network] = network + info[:network_name] = "HostInterfaceNetworking-#{network}" + elsif ip = line[/^IP:\s+(.+?)$/, 1] + info[:ip] = ip + elsif netmask = line[/^NetworkMask:\s+(.+?)$/, 1] + info[:netmask] = netmask + elsif lower = line[/^lowerIPAddress:\s+(.+?)$/, 1] + info[:lower] = lower + elsif upper = line[/^upperIPAddress:\s+(.+?)$/, 1] + info[:upper] = upper + end + end + + info + end + end + def read_guest_additions_version output = execute("guestproperty", "get", @uuid, "/VirtualBox/GuestAdd/Version", retryable: true) @@ -269,7 +292,12 @@ module VagrantPlugins end def read_guest_ip(adapter_number) - read_guest_property("/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP") + ip = read_guest_property("/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP") + if !valid_ip_address?(ip) + raise Vagrant::Errors::VirtualBoxGuestPropertyNotFound, guest_property: "/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP" + end + + return ip end def read_guest_property(property) @@ -282,26 +310,6 @@ module VagrantPlugins end def read_host_only_interfaces - dhcp = {} - execute("list", "dhcpservers", retryable: true).split("\n\n").each do |block| - info = {} - - block.split("\n").each do |line| - if network = line[/^NetworkName:\s+HostInterfaceNetworking-(.+?)$/, 1] - info[:network] = network - elsif ip = line[/^IP:\s+(.+?)$/, 1] - info[:ip] = ip - elsif lower = line[/^lowerIPAddress:\s+(.+?)$/, 1] - info[:lower] = lower - elsif upper = line[/^upperIPAddress:\s+(.+?)$/, 1] - info[:upper] = upper - end - end - - # Set the DHCP info - dhcp[info[:network]] = info - end - execute("list", "hostonlyifs", retryable: true).split("\n\n").collect do |block| info = {} @@ -317,9 +325,6 @@ module VagrantPlugins end end - # Set the DHCP info if it exists - info[:dhcp] = dhcp[info[:name]] if dhcp[info[:name]] - info end end @@ -424,6 +429,10 @@ module VagrantPlugins results end + def remove_dhcp_server(network_name) + execute("dhcpserver", "remove", "--netname", network_name) + end + def set_mac_address(mac) execute("modifyvm", @uuid, "--macaddress1", mac) end @@ -438,7 +447,7 @@ module VagrantPlugins folder[:name], "--hostpath", folder[:hostpath]] - args << "--transient" if folder.has_key?(:transient) && folder[:transient] + args << "--transient" if folder.key?(:transient) && folder[:transient] execute("sharedfolder", "add", @uuid, *args) end end @@ -500,6 +509,16 @@ module VagrantPlugins end end + def valid_ip_address?(ip) + # Filter out invalid IP addresses + # GH-4658 VirtualBox can report an IP address of 0.0.0.0 for FreeBSD guests. + if ip == "0.0.0.0" + return false + else + return true + end + end + def verify! # This command sometimes fails if kernel drivers aren't properly loaded # so we just run the command and verify that it succeeded. diff --git a/plugins/providers/virtualbox/driver/version_4_1.rb b/plugins/providers/virtualbox/driver/version_4_1.rb index 60bbdadad..ae1830019 100644 --- a/plugins/providers/virtualbox/driver/version_4_1.rb +++ b/plugins/providers/virtualbox/driver/version_4_1.rb @@ -260,6 +260,29 @@ module VagrantPlugins end end + def read_dhcp_servers + execute("list", "dhcpservers", retryable: true).split("\n\n").collect do |block| + info = {} + + block.split("\n").each do |line| + if network = line[/^NetworkName:\s+HostInterfaceNetworking-(.+?)$/, 1] + info[:network] = network + info[:network_name] = "HostInterfaceNetworking-#{network}" + elsif ip = line[/^IP:\s+(.+?)$/, 1] + info[:ip] = ip + elsif netmask = line[/^NetworkMask:\s+(.+?)$/, 1] + info[:netmask] = netmask + elsif lower = line[/^lowerIPAddress:\s+(.+?)$/, 1] + info[:lower] = lower + elsif upper = line[/^upperIPAddress:\s+(.+?)$/, 1] + info[:upper] = upper + end + end + + info + end + end + def read_guest_additions_version output = execute("guestproperty", "get", @uuid, "/VirtualBox/GuestAdd/Version", retryable: true) @@ -274,7 +297,12 @@ module VagrantPlugins end def read_guest_ip(adapter_number) - read_guest_property("/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP") + ip = read_guest_property("/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP") + if !valid_ip_address?(ip) + raise Vagrant::Errors::VirtualBoxGuestPropertyNotFound, guest_property: "/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP" + end + + return ip end def read_guest_property(property) @@ -287,26 +315,6 @@ module VagrantPlugins end def read_host_only_interfaces - dhcp = {} - execute("list", "dhcpservers", retryable: true).split("\n\n").each do |block| - info = {} - - block.split("\n").each do |line| - if network = line[/^NetworkName:\s+HostInterfaceNetworking-(.+?)$/, 1] - info[:network] = network - elsif ip = line[/^IP:\s+(.+?)$/, 1] - info[:ip] = ip - elsif lower = line[/^lowerIPAddress:\s+(.+?)$/, 1] - info[:lower] = lower - elsif upper = line[/^upperIPAddress:\s+(.+?)$/, 1] - info[:upper] = upper - end - end - - # Set the DHCP info - dhcp[info[:network]] = info - end - execute("list", "hostonlyifs", retryable: true).split("\n\n").collect do |block| info = {} @@ -322,9 +330,6 @@ module VagrantPlugins end end - # Set the DHCP info if it exists - info[:dhcp] = dhcp[info[:name]] if dhcp[info[:name]] - info end end @@ -429,6 +434,10 @@ module VagrantPlugins results end + def remove_dhcp_server(network_name) + execute("dhcpserver", "remove", "--netname", network_name) + end + def set_mac_address(mac) execute("modifyvm", @uuid, "--macaddress1", mac) end @@ -443,13 +452,13 @@ module VagrantPlugins folder[:name], "--hostpath", folder[:hostpath]] - args << "--transient" if folder.has_key?(:transient) && folder[:transient] - - # Add the shared folder - execute("sharedfolder", "add", @uuid, *args) + args << "--transient" if folder.key?(:transient) && folder[:transient] # Enable symlinks on the shared folder execute("setextradata", @uuid, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{folder[:name]}", "1") + + # Add the shared folder + execute("sharedfolder", "add", @uuid, *args) end end @@ -510,6 +519,16 @@ module VagrantPlugins end end + def valid_ip_address?(ip) + # Filter out invalid IP addresses + # GH-4658 VirtualBox can report an IP address of 0.0.0.0 for FreeBSD guests. + if ip == "0.0.0.0" + return false + else + return true + end + end + def verify! # This command sometimes fails if kernel drivers aren't properly loaded # so we just run the command and verify that it succeeded. diff --git a/plugins/providers/virtualbox/driver/version_4_2.rb b/plugins/providers/virtualbox/driver/version_4_2.rb index cbc397f3e..dc7da430a 100644 --- a/plugins/providers/virtualbox/driver/version_4_2.rb +++ b/plugins/providers/virtualbox/driver/version_4_2.rb @@ -189,9 +189,9 @@ module VagrantPlugins # we use the block form of sub here to ensure that if the specified_name happens to end with a number (which is fairly likely) then # we won't end up having the character sequence of a \ followed by a number be interpreted as a back reference. For example, if # specified_name were "abc123", then "\\abc123\\".reverse would be "\\321cba\\", and the \3 would be treated as a back reference by the sub - disk_params << path.reverse.sub("\\#{suggested_name}\\".reverse) { "\\#{specified_name}\\".reverse }.reverse # Replace only last occurrence + disk_params << path.reverse.sub("\\#{suggested_name}\\".reverse) { "\\#{specified_name}\\".reverse }.reverse # Replace only last occurrence else - disk_params << path.reverse.sub("/#{suggested_name}/".reverse, "/#{specified_name}/".reverse).reverse # Replace only last occurrence + disk_params << path.reverse.sub("/#{suggested_name}/".reverse, "/#{specified_name}/".reverse).reverse # Replace only last occurrence end end @@ -283,6 +283,29 @@ module VagrantPlugins end end + def read_dhcp_servers + execute("list", "dhcpservers", retryable: true).split("\n\n").collect do |block| + info = {} + + block.split("\n").each do |line| + if network = line[/^NetworkName:\s+HostInterfaceNetworking-(.+?)$/, 1] + info[:network] = network + info[:network_name] = "HostInterfaceNetworking-#{network}" + elsif ip = line[/^IP:\s+(.+?)$/, 1] + info[:ip] = ip + elsif netmask = line[/^NetworkMask:\s+(.+?)$/, 1] + info[:netmask] = netmask + elsif lower = line[/^lowerIPAddress:\s+(.+?)$/, 1] + info[:lower] = lower + elsif upper = line[/^upperIPAddress:\s+(.+?)$/, 1] + info[:upper] = upper + end + end + + info + end + end + def read_guest_additions_version output = execute("guestproperty", "get", @uuid, "/VirtualBox/GuestAdd/Version", retryable: true) @@ -305,7 +328,12 @@ module VagrantPlugins end def read_guest_ip(adapter_number) - read_guest_property("/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP") + ip = read_guest_property("/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP") + if !valid_ip_address?(ip) + raise Vagrant::Errors::VirtualBoxGuestPropertyNotFound, guest_property: "/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP" + end + + return ip end def read_guest_property(property) @@ -318,26 +346,6 @@ module VagrantPlugins end def read_host_only_interfaces - dhcp = {} - execute("list", "dhcpservers", retryable: true).split("\n\n").each do |block| - info = {} - - block.split("\n").each do |line| - if line =~ /^NetworkName:\s+HostInterfaceNetworking-(.+?)$/ - info[:network] = $1.to_s - elsif line =~ /^IP:\s+(.+?)$/ - info[:ip] = $1.to_s - elsif line =~ /^lowerIPAddress:\s+(.+?)$/ - info[:lower] = $1.to_s - elsif line =~ /^upperIPAddress:\s+(.+?)$/ - info[:upper] = $1.to_s - end - end - - # Set the DHCP info - dhcp[info[:network]] = info - end - execute("list", "hostonlyifs", retryable: true).split("\n\n").collect do |block| info = {} @@ -353,9 +361,6 @@ module VagrantPlugins end end - # Set the DHCP info if it exists - info[:dhcp] = dhcp[info[:name]] if dhcp[info[:name]] - info end end @@ -460,6 +465,10 @@ module VagrantPlugins results end + def remove_dhcp_server(network_name) + execute("dhcpserver", "remove", "--netname", network_name) + end + def set_mac_address(mac) execute("modifyvm", @uuid, "--macaddress1", mac) end @@ -474,13 +483,13 @@ module VagrantPlugins folder[:name], "--hostpath", folder[:hostpath]] - args << "--transient" if folder.has_key?(:transient) && folder[:transient] - - # Add the shared folder - execute("sharedfolder", "add", @uuid, *args) + args << "--transient" if folder.key?(:transient) && folder[:transient] # Enable symlinks on the shared folder execute("setextradata", @uuid, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{folder[:name]}", "1") + + # Add the shared folder + execute("sharedfolder", "add", @uuid, *args) end end @@ -541,6 +550,16 @@ module VagrantPlugins end end + def valid_ip_address?(ip) + # Filter out invalid IP addresses + # GH-4658 VirtualBox can report an IP address of 0.0.0.0 for FreeBSD guests. + if ip == "0.0.0.0" + return false + else + return true + end + end + def verify! # This command sometimes fails if kernel drivers aren't properly loaded # so we just run the command and verify that it succeeded. diff --git a/plugins/providers/virtualbox/driver/version_4_3.rb b/plugins/providers/virtualbox/driver/version_4_3.rb index 76ccdcd6a..b083637d1 100644 --- a/plugins/providers/virtualbox/driver/version_4_3.rb +++ b/plugins/providers/virtualbox/driver/version_4_3.rb @@ -309,6 +309,29 @@ module VagrantPlugins end end + def read_dhcp_servers + execute("list", "dhcpservers", retryable: true).split("\n\n").collect do |block| + info = {} + + block.split("\n").each do |line| + if network = line[/^NetworkName:\s+HostInterfaceNetworking-(.+?)$/, 1] + info[:network] = network + info[:network_name] = "HostInterfaceNetworking-#{network}" + elsif ip = line[/^IP:\s+(.+?)$/, 1] + info[:ip] = ip + elsif netmask = line[/^NetworkMask:\s+(.+?)$/, 1] + info[:netmask] = netmask + elsif lower = line[/^lowerIPAddress:\s+(.+?)$/, 1] + info[:lower] = lower + elsif upper = line[/^upperIPAddress:\s+(.+?)$/, 1] + info[:upper] = upper + end + end + + info + end + end + def read_guest_additions_version output = execute("guestproperty", "get", @uuid, "/VirtualBox/GuestAdd/Version", retryable: true) @@ -331,7 +354,13 @@ module VagrantPlugins end def read_guest_ip(adapter_number) - read_guest_property("/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP") + ip = read_guest_property("/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP") + if !valid_ip_address?(ip) + raise Vagrant::Errors::VirtualBoxGuestPropertyNotFound, + guest_property: "/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP" + end + + return ip end def read_guest_property(property) @@ -344,26 +373,6 @@ module VagrantPlugins end def read_host_only_interfaces - dhcp = {} - execute("list", "dhcpservers", retryable: true).split("\n\n").each do |block| - info = {} - - block.split("\n").each do |line| - if line =~ /^NetworkName:\s+HostInterfaceNetworking-(.+?)$/ - info[:network] = $1.to_s - elsif line =~ /^IP:\s+(.+?)$/ - info[:ip] = $1.to_s - elsif line =~ /^lowerIPAddress:\s+(.+?)$/ - info[:lower] = $1.to_s - elsif line =~ /^upperIPAddress:\s+(.+?)$/ - info[:upper] = $1.to_s - end - end - - # Set the DHCP info - dhcp[info[:network]] = info - end - execute("list", "hostonlyifs", retryable: true).split("\n\n").collect do |block| info = {} @@ -379,9 +388,6 @@ module VagrantPlugins end end - # Set the DHCP info if it exists - info[:dhcp] = dhcp[info[:name]] if dhcp[info[:name]] - info end end @@ -486,27 +492,42 @@ module VagrantPlugins results end + def remove_dhcp_server(network_name) + execute("dhcpserver", "remove", "--netname", network_name) + end + def set_mac_address(mac) execute("modifyvm", @uuid, "--macaddress1", mac) end def set_name(name) execute("modifyvm", @uuid, "--name", name, retryable: true) + rescue Vagrant::Errors::VBoxManageError => e + raise if !e.extra_data[:stderr].include?("VERR_ALREADY_EXISTS") + + # We got VERR_ALREADY_EXISTS. This means that we're renaming to + # a VM name that already exists. Raise a custom error. + raise Vagrant::Errors::VirtualBoxNameExists, + stderr: e.extra_data[:stderr] end def share_folders(folders) folders.each do |folder| + hostpath = folder[:hostpath] + if Vagrant::Util::Platform.windows? + hostpath = Vagrant::Util::Platform.windows_unc_path(hostpath) + end args = ["--name", folder[:name], "--hostpath", - folder[:hostpath]] - args << "--transient" if folder.has_key?(:transient) && folder[:transient] - - # Add the shared folder - execute("sharedfolder", "add", @uuid, *args) + hostpath] + args << "--transient" if folder.key?(:transient) && folder[:transient] # Enable symlinks on the shared folder execute("setextradata", @uuid, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{folder[:name]}", "1") + + # Add the shared folder + execute("sharedfolder", "add", @uuid, *args) end end @@ -597,6 +618,18 @@ module VagrantPlugins execute("showvminfo", uuid) return true end + + protected + + def valid_ip_address?(ip) + # Filter out invalid IP addresses + # GH-4658 VirtualBox can report an IP address of 0.0.0.0 for FreeBSD guests. + if ip == "0.0.0.0" + return false + else + return true + end + end end end end diff --git a/plugins/providers/virtualbox/driver/version_5_0.rb b/plugins/providers/virtualbox/driver/version_5_0.rb new file mode 100644 index 000000000..d7d3b58df --- /dev/null +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -0,0 +1,619 @@ +require 'log4r' + +require "vagrant/util/platform" + +require File.expand_path("../base", __FILE__) + +module VagrantPlugins + module ProviderVirtualBox + module Driver + # Driver for VirtualBox 5.0.x + class Version_5_0 < Base + def initialize(uuid) + super() + + @logger = Log4r::Logger.new("vagrant::provider::virtualbox_5_0") + @uuid = uuid + end + + def clear_forwarded_ports + args = [] + read_forwarded_ports(@uuid).each do |nic, name, _, _| + args.concat(["--natpf#{nic}", "delete", name]) + end + + execute("modifyvm", @uuid, *args) if !args.empty? + end + + def clear_shared_folders + info = execute("showvminfo", @uuid, "--machinereadable", retryable: true) + info.split("\n").each do |line| + if line =~ /^SharedFolderNameMachineMapping\d+="(.+?)"$/ + execute("sharedfolder", "remove", @uuid, "--name", $1.to_s) + end + end + end + + def create_dhcp_server(network, options) + execute("dhcpserver", "add", "--ifname", network, + "--ip", options[:dhcp_ip], + "--netmask", options[:netmask], + "--lowerip", options[:dhcp_lower], + "--upperip", options[:dhcp_upper], + "--enable") + end + + def create_host_only_network(options) + # Create the interface + execute("hostonlyif", "create") =~ /^Interface '(.+?)' was successfully created$/ + name = $1.to_s + + # Configure it + execute("hostonlyif", "ipconfig", name, + "--ip", options[:adapter_ip], + "--netmask", options[:netmask]) + + # Return the details + return { + name: name, + ip: options[:adapter_ip], + netmask: options[:netmask], + dhcp: nil + } + end + + def delete + execute("unregistervm", @uuid, "--delete") + end + + def delete_unused_host_only_networks + networks = [] + execute("list", "hostonlyifs", retryable: true).split("\n").each do |line| + networks << $1.to_s if line =~ /^Name:\s+(.+?)$/ + end + + execute("list", "vms", retryable: true).split("\n").each do |line| + if line =~ /^".+?"\s+\{(.+?)\}$/ + info = execute("showvminfo", $1.to_s, "--machinereadable", retryable: true) + info.split("\n").each do |inner_line| + if inner_line =~ /^hostonlyadapter\d+="(.+?)"$/ + networks.delete($1.to_s) + end + end + end + end + + networks.each do |name| + # First try to remove any DHCP servers attached. We use `raw` because + # it is okay if this fails. It usually means that a DHCP server was + # never attached. + raw("dhcpserver", "remove", "--ifname", name) + + # Delete the actual host only network interface. + execute("hostonlyif", "remove", name) + end + end + + def discard_saved_state + execute("discardstate", @uuid) + end + + def enable_adapters(adapters) + args = [] + adapters.each do |adapter| + args.concat(["--nic#{adapter[:adapter]}", adapter[:type].to_s]) + + if adapter[:bridge] + args.concat(["--bridgeadapter#{adapter[:adapter]}", + adapter[:bridge], "--cableconnected#{adapter[:adapter]}", "on"]) + end + + if adapter[:hostonly] + args.concat(["--hostonlyadapter#{adapter[:adapter]}", + adapter[:hostonly], "--cableconnected#{adapter[:adapter]}", "on"]) + end + + if adapter[:intnet] + args.concat(["--intnet#{adapter[:adapter]}", + adapter[:intnet], "--cableconnected#{adapter[:adapter]}", "on"]) + end + + if adapter[:mac_address] + args.concat(["--macaddress#{adapter[:adapter]}", + adapter[:mac_address]]) + end + + if adapter[:nic_type] + args.concat(["--nictype#{adapter[:adapter]}", adapter[:nic_type].to_s]) + end + end + + execute("modifyvm", @uuid, *args) + end + + def execute_command(command) + execute(*command) + end + + def export(path) + execute("export", @uuid, "--output", path.to_s) + end + + def forward_ports(ports) + args = [] + ports.each do |options| + pf_builder = [options[:name], + options[:protocol] || "tcp", + options[:hostip] || "", + options[:hostport], + options[:guestip] || "", + options[:guestport]] + + args.concat(["--natpf#{options[:adapter] || 1}", + pf_builder.join(",")]) + end + + execute("modifyvm", @uuid, *args) if !args.empty? + end + + def halt + execute("controlvm", @uuid, "poweroff") + end + + def import(ovf) + ovf = Vagrant::Util::Platform.cygwin_windows_path(ovf) + + output = "" + total = "" + last = 0 + + # Dry-run the import to get the suggested name and path + @logger.debug("Doing dry-run import to determine parallel-safe name...") + output = execute("import", "-n", ovf) + result = /Suggested VM name "(.+?)"/.match(output) + if !result + raise Vagrant::Errors::VirtualBoxNoName, output: output + end + suggested_name = result[1].to_s + + # Append millisecond plus a random to the path in case we're + # importing the same box elsewhere. + specified_name = "#{suggested_name}_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100000)}" + @logger.debug("-- Parallel safe name: #{specified_name}") + + # Build the specified name param list + name_params = [ + "--vsys", "0", + "--vmname", specified_name, + ] + + # Extract the disks list and build the disk target params + disk_params = [] + disks = output.scan(/(\d+): Hard disk image: source image=.+, target path=(.+),/) + disks.each do |unit_num, path| + disk_params << "--vsys" + disk_params << "0" + disk_params << "--unit" + disk_params << unit_num + disk_params << "--disk" + if Vagrant::Util::Platform.windows? + # we use the block form of sub here to ensure that if the specified_name happens to end with a number (which is fairly likely) then + # we won't end up having the character sequence of a \ followed by a number be interpreted as a back reference. For example, if + # specified_name were "abc123", then "\\abc123\\".reverse would be "\\321cba\\", and the \3 would be treated as a back reference by the sub + disk_params << path.reverse.sub("\\#{suggested_name}\\".reverse) { "\\#{specified_name}\\".reverse }.reverse # Replace only last occurrence + else + disk_params << path.reverse.sub("/#{suggested_name}/".reverse, "/#{specified_name}/".reverse).reverse # Replace only last occurrence + end + end + + execute("import", ovf , *name_params, *disk_params) do |type, data| + if type == :stdout + # Keep track of the stdout so that we can get the VM name + output << data + elsif type == :stderr + # Append the data so we can see the full view + total << data.gsub("\r", "") + + # Break up the lines. We can't get the progress until we see an "OK" + lines = total.split("\n") + if lines.include?("OK.") + # The progress of the import will be in the last line. Do a greedy + # regular expression to find what we're looking for. + match = /.+(\d{2})%/.match(lines.last) + if match + current = match[1].to_i + if current > last + last = current + yield current if block_given? + end + end + end + end + end + + output = execute("list", "vms", retryable: true) + match = /^"#{Regexp.escape(specified_name)}" \{(.+?)\}$/.match(output) + return match[1].to_s if match + nil + end + + def max_network_adapters + 36 + end + + def read_forwarded_ports(uuid=nil, active_only=false) + uuid ||= @uuid + + @logger.debug("read_forward_ports: uuid=#{uuid} active_only=#{active_only}") + + results = [] + current_nic = nil + info = execute("showvminfo", uuid, "--machinereadable", retryable: true) + info.split("\n").each do |line| + # This is how we find the nic that a FP is attached to, + # since this comes first. + current_nic = $1.to_i if line =~ /^nic(\d+)=".+?"$/ + + # If we care about active VMs only, then we check the state + # to verify the VM is running. + if active_only && line =~ /^VMState="(.+?)"$/ && $1.to_s != "running" + return [] + end + + # Parse out the forwarded port information + if line =~ /^Forwarding.+?="(.+?),.+?,.*?,(.+?),.*?,(.+?)"$/ + result = [current_nic, $1.to_s, $2.to_i, $3.to_i] + @logger.debug(" - #{result.inspect}") + results << result + end + end + + results + end + + def read_bridged_interfaces + execute("list", "bridgedifs").split("\n\n").collect do |block| + info = {} + + block.split("\n").each do |line| + if line =~ /^Name:\s+(.+?)$/ + info[:name] = $1.to_s + elsif line =~ /^IPAddress:\s+(.+?)$/ + info[:ip] = $1.to_s + elsif line =~ /^NetworkMask:\s+(.+?)$/ + info[:netmask] = $1.to_s + elsif line =~ /^Status:\s+(.+?)$/ + info[:status] = $1.to_s + end + end + + # Return the info to build up the results + info + end + end + + def read_dhcp_servers + execute("list", "dhcpservers", retryable: true).split("\n\n").collect do |block| + info = {} + + block.split("\n").each do |line| + if network = line[/^NetworkName:\s+HostInterfaceNetworking-(.+?)$/, 1] + info[:network] = network + info[:network_name] = "HostInterfaceNetworking-#{network}" + elsif ip = line[/^IP:\s+(.+?)$/, 1] + info[:ip] = ip + elsif netmask = line[/^NetworkMask:\s+(.+?)$/, 1] + info[:netmask] = netmask + elsif lower = line[/^lowerIPAddress:\s+(.+?)$/, 1] + info[:lower] = lower + elsif upper = line[/^upperIPAddress:\s+(.+?)$/, 1] + info[:upper] = upper + end + end + + info + end + end + + def read_guest_additions_version + output = execute("guestproperty", "get", @uuid, "/VirtualBox/GuestAdd/Version", + retryable: true) + if output =~ /^Value: (.+?)$/ + # Split the version by _ since some distro versions modify it + # to look like this: 4.1.2_ubuntu, and the distro part isn't + # too important. + value = $1.to_s + return value.split("_").first + end + + # If we can't get the guest additions version by guest property, try + # to get it from the VM info itself. + info = execute("showvminfo", @uuid, "--machinereadable", retryable: true) + info.split("\n").each do |line| + return $1.to_s if line =~ /^GuestAdditionsVersion="(.+?)"$/ + end + + return nil + end + + def read_guest_ip(adapter_number) + ip = read_guest_property("/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP") + if !valid_ip_address?(ip) + raise Vagrant::Errors::VirtualBoxGuestPropertyNotFound, + guest_property: "/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP" + end + + return ip + end + + def read_guest_property(property) + output = execute("guestproperty", "get", @uuid, property) + if output =~ /^Value: (.+?)$/ + $1.to_s + else + raise Vagrant::Errors::VirtualBoxGuestPropertyNotFound, guest_property: property + end + end + + def read_host_only_interfaces + execute("list", "hostonlyifs", retryable: true).split("\n\n").collect do |block| + info = {} + + block.split("\n").each do |line| + if line =~ /^Name:\s+(.+?)$/ + info[:name] = $1.to_s + elsif line =~ /^IPAddress:\s+(.+?)$/ + info[:ip] = $1.to_s + elsif line =~ /^NetworkMask:\s+(.+?)$/ + info[:netmask] = $1.to_s + elsif line =~ /^Status:\s+(.+?)$/ + info[:status] = $1.to_s + end + end + + info + end + end + + def read_mac_address + info = execute("showvminfo", @uuid, "--machinereadable", retryable: true) + info.split("\n").each do |line| + return $1.to_s if line =~ /^macaddress1="(.+?)"$/ + end + + nil + end + + def read_mac_addresses + macs = {} + info = execute("showvminfo", @uuid, "--machinereadable", retryable: true) + info.split("\n").each do |line| + if matcher = /^macaddress(\d+)="(.+?)"$/.match(line) + adapter = matcher[1].to_i + mac = matcher[2].to_s + macs[adapter] = mac + end + end + macs + end + + def read_machine_folder + execute("list", "systemproperties", retryable: true).split("\n").each do |line| + if line =~ /^Default machine folder:\s+(.+?)$/i + return $1.to_s + end + end + + nil + end + + def read_network_interfaces + nics = {} + info = execute("showvminfo", @uuid, "--machinereadable", retryable: true) + info.split("\n").each do |line| + if line =~ /^nic(\d+)="(.+?)"$/ + adapter = $1.to_i + type = $2.to_sym + + nics[adapter] ||= {} + nics[adapter][:type] = type + elsif line =~ /^hostonlyadapter(\d+)="(.+?)"$/ + adapter = $1.to_i + network = $2.to_s + + nics[adapter] ||= {} + nics[adapter][:hostonly] = network + elsif line =~ /^bridgeadapter(\d+)="(.+?)"$/ + adapter = $1.to_i + network = $2.to_s + + nics[adapter] ||= {} + nics[adapter][:bridge] = network + end + end + + nics + end + + def read_state + output = execute("showvminfo", @uuid, "--machinereadable", retryable: true) + if output =~ /^name=""$/ + return :inaccessible + elsif output =~ /^VMState="(.+?)"$/ + return $1.to_sym + end + + nil + end + + def read_used_ports + ports = [] + execute("list", "vms", retryable: true).split("\n").each do |line| + if line =~ /^".+?" \{(.+?)\}$/ + uuid = $1.to_s + + # Ignore our own used ports + next if uuid == @uuid + + read_forwarded_ports(uuid, true).each do |_, _, hostport, _| + ports << hostport + end + end + end + + ports + end + + def read_vms + results = {} + execute("list", "vms", retryable: true).split("\n").each do |line| + if line =~ /^"(.+?)" \{(.+?)\}$/ + results[$1.to_s] = $2.to_s + end + end + + results + end + + def remove_dhcp_server(network_name) + execute("dhcpserver", "remove", "--netname", network_name) + end + + def set_mac_address(mac) + execute("modifyvm", @uuid, "--macaddress1", mac) + end + + def set_name(name) + execute("modifyvm", @uuid, "--name", name, retryable: true) + rescue Vagrant::Errors::VBoxManageError => e + raise if !e.extra_data[:stderr].include?("VERR_ALREADY_EXISTS") + + # We got VERR_ALREADY_EXISTS. This means that we're renaming to + # a VM name that already exists. Raise a custom error. + raise Vagrant::Errors::VirtualBoxNameExists, + stderr: e.extra_data[:stderr] + end + + def share_folders(folders) + folders.each do |folder| + hostpath = folder[:hostpath] + if Vagrant::Util::Platform.windows? + hostpath = Vagrant::Util::Platform.windows_unc_path(hostpath) + end + args = ["--name", + folder[:name], + "--hostpath", + hostpath] + args << "--transient" if folder.key?(:transient) && folder[:transient] + + # Enable symlinks on the shared folder + execute("setextradata", @uuid, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{folder[:name]}", "1") + + # Add the shared folder + execute("sharedfolder", "add", @uuid, *args) + end + end + + def ssh_port(expected_port) + @logger.debug("Searching for SSH port: #{expected_port.inspect}") + + # Look for the forwarded port only by comparing the guest port + read_forwarded_ports.each do |_, _, hostport, guestport| + return hostport if guestport == expected_port + end + + nil + end + + def resume + @logger.debug("Resuming paused VM...") + execute("controlvm", @uuid, "resume") + end + + def start(mode) + command = ["startvm", @uuid, "--type", mode.to_s] + r = raw(*command) + + if r.exit_code == 0 || r.stdout =~ /VM ".+?" has been successfully started/ + # Some systems return an exit code 1 for some reason. For that + # we depend on the output. + return true + end + + # If we reached this point then it didn't work out. + raise Vagrant::Errors::VBoxManageError, + command: command.inspect, + stderr: r.stderr + end + + def suspend + execute("controlvm", @uuid, "savestate") + end + + def unshare_folders(names) + names.each do |name| + begin + execute( + "sharedfolder", "remove", @uuid, + "--name", name, + "--transient") + + execute( + "setextradata", @uuid, + "VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{name}") + rescue Vagrant::Errors::VBoxManageError => e + if e.extra_data[:stderr].include?("VBOX_E_FILE_ERROR") + # The folder doesn't exist. ignore. + else + raise + end + end + end + end + + def verify! + # This command sometimes fails if kernel drivers aren't properly loaded + # so we just run the command and verify that it succeeded. + execute("list", "hostonlyifs", retryable: true) + end + + def verify_image(path) + r = raw("import", path.to_s, "--dry-run") + return r.exit_code == 0 + end + + def vm_exists?(uuid) + 5.times do |i| + result = raw("showvminfo", uuid) + return true if result.exit_code == 0 + + # GH-2479: Sometimes this happens. In this case, retry. If + # we don't see this text, the VM really probably doesn't exist. + return false if !result.stderr.include?("CO_E_SERVER_EXEC_FAILURE") + + # Sleep a bit though to give VirtualBox time to fix itself + sleep 2 + end + + # If we reach this point, it means that we consistently got the + # failure, do a standard vboxmanage now. This will raise an + # exception if it fails again. + execute("showvminfo", uuid) + return true + end + + protected + + def valid_ip_address?(ip) + # Filter out invalid IP addresses + # GH-4658 VirtualBox can report an IP address of 0.0.0.0 for FreeBSD guests. + if ip == "0.0.0.0" + return false + else + return true + end + end + end + end + end +end diff --git a/plugins/providers/virtualbox/model/forwarded_port.rb b/plugins/providers/virtualbox/model/forwarded_port.rb index 2229519f7..03107408e 100644 --- a/plugins/providers/virtualbox/model/forwarded_port.rb +++ b/plugins/providers/virtualbox/model/forwarded_port.rb @@ -51,7 +51,7 @@ module VagrantPlugins options ||= {} @auto_correct = false - @auto_correct = options[:auto_correct] if options.has_key?(:auto_correct) + @auto_correct = options[:auto_correct] if options.key?(:auto_correct) @adapter = (options[:adapter] || 1).to_i @guest_ip = options[:guest_ip] || nil @host_ip = options[:host_ip] || nil diff --git a/plugins/providers/virtualbox/plugin.rb b/plugins/providers/virtualbox/plugin.rb index 7fc0ea039..18e33d4bb 100644 --- a/plugins/providers/virtualbox/plugin.rb +++ b/plugins/providers/virtualbox/plugin.rb @@ -45,6 +45,7 @@ module VagrantPlugins autoload :Version_4_1, File.expand_path("../driver/version_4_1", __FILE__) autoload :Version_4_2, File.expand_path("../driver/version_4_2", __FILE__) autoload :Version_4_3, File.expand_path("../driver/version_4_3", __FILE__) + autoload :Version_5_0, File.expand_path("../driver/version_5_0", __FILE__) end module Model diff --git a/plugins/providers/virtualbox/provider.rb b/plugins/providers/virtualbox/provider.rb index 9428fe6d7..969fb4718 100644 --- a/plugins/providers/virtualbox/provider.rb +++ b/plugins/providers/virtualbox/provider.rb @@ -73,6 +73,15 @@ module VagrantPlugins # # @return [Symbol] def state + # We have to check if the UID matches to avoid issues with + # VirtualBox. + uid = @machine.uid + if uid && uid.to_s != Process.uid.to_s + raise Vagrant::Errors::VirtualBoxUserMismatch, + original_uid: uid.to_s, + uid: Process.uid.to_s + end + # Determine the ID of the state here. state_id = nil state_id = :not_created if !@driver.uuid diff --git a/plugins/provisioners/ansible/provisioner.rb b/plugins/provisioners/ansible/provisioner.rb index 76f07ea65..6000621af 100644 --- a/plugins/provisioners/ansible/provisioner.rb +++ b/plugins/provisioners/ansible/provisioner.rb @@ -1,7 +1,12 @@ +require "vagrant/util/platform" +require "thread" + module VagrantPlugins module Ansible class Provisioner < Vagrant.plugin("2", :provisioner) + @@lock = Mutex.new + def initialize(machine, config) super @@ -12,31 +17,30 @@ module VagrantPlugins @ssh_info = @machine.ssh_info # - # 1) Default Settings (lowest precedence) + # Ansible provisioner options # - # Connect with Vagrant SSH identity - options = %W[--private-key=#{@ssh_info[:private_key_path][0]} --user=#{@ssh_info[:username]}] + # By default, connect with Vagrant SSH username + options = %W[--user=#{@ssh_info[:username]}] - # Multiple SSH keys and/or SSH forwarding can be passed via - # ANSIBLE_SSH_ARGS environment variable, which requires 'ssh' mode. - # Note that multiple keys and ssh-forwarding settings are not supported - # by deprecated 'paramiko' mode. - options << "--connection=ssh" unless ansible_ssh_args.empty? + # Connect with native OpenSSH client + # Other modes (e.g. paramiko) are not officially supported, + # but can be enabled via raw_arguments option. + options << "--connection=ssh" - # By default we limit by the current machine. - # This can be overridden by the limit config option. - options << "--limit=#{@machine.name}" unless config.limit + # Increase the SSH connection timeout, as the Ansible default value (10 seconds) + # is a bit demanding for some overloaded developer boxes. This is particularly + # helpful when additional virtual networks are configured, as their availability + # is not controlled during vagrant boot process. + options << "--timeout=30" - # - # 2) Configuration Joker - # - - options.concat(self.as_array(config.raw_arguments)) if config.raw_arguments - - # - # 3) Append Provisioner options (highest precedence): - # + # By default we limit by the current machine, but + # this can be overridden by the `limit` option. + if config.limit + options << "--limit=#{as_list_argument(config.limit)}" + else + options << "--limit=#{@machine.name}" + end options << "--inventory-file=#{self.setup_inventory_file}" options << "--extra-vars=#{self.get_extra_vars_argument}" if config.extra_vars @@ -48,22 +52,36 @@ module VagrantPlugins options << "--vault-password-file=#{config.vault_password_file}" if config.vault_password_file options << "--tags=#{as_list_argument(config.tags)}" if config.tags options << "--skip-tags=#{as_list_argument(config.skip_tags)}" if config.skip_tags - options << "--limit=#{as_list_argument(config.limit)}" if config.limit options << "--start-at-task=#{config.start_at_task}" if config.start_at_task + # Finally, add the raw configuration options, which has the highest precedence + # and can therefore potentially override any other options of this provisioner. + options.concat(self.as_array(config.raw_arguments)) if config.raw_arguments + + # # Assemble the full ansible-playbook command + # + command = (%w(ansible-playbook) << options << config.playbook).flatten - # Some Ansible options must be passed as environment variables env = { - "ANSIBLE_FORCE_COLOR" => "true", - "ANSIBLE_HOST_KEY_CHECKING" => "#{config.host_key_checking}", - # Ensure Ansible output isn't buffered so that we receive output # on a task-by-task basis. - "PYTHONUNBUFFERED" => 1 + "PYTHONUNBUFFERED" => 1, + + # Some Ansible options must be passed as environment variables, + # as there is no equivalent command line arguments + "ANSIBLE_HOST_KEY_CHECKING" => "#{config.host_key_checking}", } - # Support Multiple SSH keys and SSH forwarding: + + # When Ansible output is piped in Vagrant integration, its default colorization is + # automatically disabled and the only way to re-enable colors is to use ANSIBLE_FORCE_COLOR. + env["ANSIBLE_FORCE_COLOR"] = "true" if @machine.env.ui.color? + # Setting ANSIBLE_NOCOLOR is "unnecessary" at the moment, but this could change in the future + # (e.g. local provisioner [GH-2103], possible change in vagrant/ansible integration, etc.) + env["ANSIBLE_NOCOLOR"] = "true" if !@machine.env.ui.color? + + # ANSIBLE_SSH_ARGS is required for Multiple SSH keys, SSH forwarding and custom SSH settings env["ANSIBLE_SSH_ARGS"] = ansible_ssh_args unless ansible_ssh_args.empty? show_ansible_playbook_command(env, command) if config.verbose @@ -102,54 +120,63 @@ module VagrantPlugins FileUtils.mkdir_p(generated_inventory_dir) unless File.directory?(generated_inventory_dir) generated_inventory_file = generated_inventory_dir.join('vagrant_ansible_inventory') - generated_inventory_file.open('w') do |file| - file.write("# Generated by Vagrant\n\n") + inventory = "# Generated by Vagrant\n\n" - @machine.env.active_machines.each do |am| - begin - m = @machine.env.machine(*am) - if !m.ssh_info.nil? - file.write("#{m.name} ansible_ssh_host=#{m.ssh_info[:host]} ansible_ssh_port=#{m.ssh_info[:port]}\n") - inventory_machines[m.name] = m - else - @logger.error("Auto-generated inventory: Impossible to get SSH information for machine '#{m.name} (#{m.provider_name})'. This machine should be recreated.") - # Let a note about this missing machine - file.write("# MISSING: '#{m.name}' machine was probably removed without using Vagrant. This machine should be recreated.\n") - end - rescue Vagrant::Errors::MachineNotFound => e - @logger.info("Auto-generated inventory: Skip machine '#{am[0]} (#{am[1]})', which is not configured for this Vagrant environment.") + @machine.env.active_machines.each do |am| + begin + m = @machine.env.machine(*am) + m_ssh_info = m.ssh_info + if !m_ssh_info.nil? + inventory += "#{m.name} ansible_ssh_host=#{m_ssh_info[:host]} ansible_ssh_port=#{m_ssh_info[:port]} ansible_ssh_private_key_file=#{m_ssh_info[:private_key_path][0]}\n" + inventory_machines[m.name] = m + else + @logger.error("Auto-generated inventory: Impossible to get SSH information for machine '#{m.name} (#{m.provider_name})'. This machine should be recreated.") + # Let a note about this missing machine + inventory += "# MISSING: '#{m.name}' machine was probably removed without using Vagrant. This machine should be recreated.\n" end + rescue Vagrant::Errors::MachineNotFound => e + @logger.info("Auto-generated inventory: Skip machine '#{am[0]} (#{am[1]})', which is not configured for this Vagrant environment.") end + end - # Write out groups information. - # All defined groups will be included, but only supported - # machines and defined child groups will be included. - # Group variables are intentionally skipped. - groups_of_groups = {} - defined_groups = [] + # Write out groups information. + # All defined groups will be included, but only supported + # machines and defined child groups will be included. + # Group variables are intentionally skipped. + groups_of_groups = {} + defined_groups = [] - config.groups.each_pair do |gname, gmembers| - # Require that gmembers be an array - # (easier to be tolerant and avoid error management of few value) - gmembers = [gmembers] if !gmembers.is_a?(Array) + config.groups.each_pair do |gname, gmembers| + # Require that gmembers be an array + # (easier to be tolerant and avoid error management of few value) + gmembers = [gmembers] if !gmembers.is_a?(Array) - if gname.end_with?(":children") - groups_of_groups[gname] = gmembers - defined_groups << gname.sub(/:children$/, '') - elsif !gname.include?(':vars') - defined_groups << gname - file.write("\n[#{gname}]\n") - gmembers.each do |gm| - file.write("#{gm}\n") if inventory_machines.include?(gm.to_sym) - end - end - end - - defined_groups.uniq! - groups_of_groups.each_pair do |gname, gmembers| - file.write("\n[#{gname}]\n") + if gname.end_with?(":children") + groups_of_groups[gname] = gmembers + defined_groups << gname.sub(/:children$/, '') + elsif !gname.include?(':vars') + defined_groups << gname + inventory += "\n[#{gname}]\n" gmembers.each do |gm| - file.write("#{gm}\n") if defined_groups.include?(gm) + inventory += "#{gm}\n" if inventory_machines.include?(gm.to_sym) + end + end + end + + defined_groups.uniq! + groups_of_groups.each_pair do |gname, gmembers| + inventory += "\n[#{gname}]\n" + gmembers.each do |gm| + inventory += "#{gm}\n" if defined_groups.include?(gm) + end + end + + @@lock.synchronize do + if ! File.exists?(generated_inventory_file) or + inventory != File.read(generated_inventory_file) + + generated_inventory_file.open('w') do |file| + file.write(inventory) end end end @@ -182,12 +209,43 @@ module VagrantPlugins @ansible_ssh_args ||= get_ansible_ssh_args end + # Use ANSIBLE_SSH_ARGS to pass some OpenSSH options that are not wrapped by + # an ad-hoc Ansible option. Last update corresponds to Ansible 1.8 def get_ansible_ssh_args ssh_options = [] + # Use an SSH ProxyCommand when using the Docker provider with the intermediate host + if @machine.provider_name == :docker && machine.provider.host_vm? + docker_host_ssh_info = machine.provider.host_vm.ssh_info + + proxy_cmd = "ssh #{docker_host_ssh_info[:username]}@#{docker_host_ssh_info[:host]}" + + " -p #{docker_host_ssh_info[:port]} -i #{docker_host_ssh_info[:private_key_path][0]}" + + # Use same options than plugins/providers/docker/communicator.rb + # Note: this could be improved (DRY'ed) by sharing these settings. + proxy_cmd += " -o Compression=yes -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" + + proxy_cmd += " -o ForwardAgent=yes" if @ssh_info[:forward_agent] + + proxy_cmd += " exec nc %h %p 2>/dev/null" + + ssh_options << "-o ProxyCommand='#{ proxy_cmd }'" + end + + # Don't access user's known_hosts file, except when host_key_checking is enabled. + ssh_options << "-o UserKnownHostsFile=/dev/null" unless config.host_key_checking + + # Set IdentitiesOnly=yes to avoid authentication errors when the host has more than 5 ssh keys. + # Notes: + # - Solaris/OpenSolaris/Illumos uses SunSSH which doesn't support the IdentitiesOnly option. + # - this could be improved by sharing logic with lib/vagrant/util/ssh.rb + ssh_options << "-o IdentitiesOnly=yes" unless Vagrant::Util::Platform.solaris? + # Multiple Private Keys - @ssh_info[:private_key_path].drop(1).each do |key| - ssh_options << "-o IdentityFile=#{key}" + unless !config.inventory_path && @ssh_info[:private_key_path].size == 1 + @ssh_info[:private_key_path].each do |key| + ssh_options << "-o IdentityFile=#{key}" + end end # SSH Forwarding diff --git a/plugins/provisioners/cfengine/cap/suse/cfengine_install.rb b/plugins/provisioners/cfengine/cap/suse/cfengine_install.rb index 278fe49a8..fd5ef1b30 100644 --- a/plugins/provisioners/cfengine/cap/suse/cfengine_install.rb +++ b/plugins/provisioners/cfengine/cap/suse/cfengine_install.rb @@ -1,12 +1,13 @@ module VagrantPlugins module CFEngine module Cap - module SuSE + module SUSE module CFEngineInstall def self.cfengine_install(machine, config) machine.communicate.tap do |comm| - comm.sudo("GPGFILE=$(mktemp) && wget -O $GPGFILE #{config.repo_gpg_key_url} && rpm --import $GPGFILE; rm -f $GPGFILE") - comm.sudo("zypper addrepo -t YUM #{config.yum_repo_url} cfengine-repository") + comm.sudo("rpm --import #{config.repo_gpg_key_url}") + + comm.sudo("zypper addrepo -t YUM #{config.yum_repo_url} CFEngine") comm.sudo("zypper se #{config.package_name} && zypper -n install #{config.package_name}") end end diff --git a/plugins/provisioners/cfengine/plugin.rb b/plugins/provisioners/cfengine/plugin.rb index 4e1a7f093..ee5d75768 100644 --- a/plugins/provisioners/cfengine/plugin.rb +++ b/plugins/provisioners/cfengine/plugin.rb @@ -35,7 +35,7 @@ module VagrantPlugins guest_capability("suse", "cfengine_install") do require_relative "cap/suse/cfengine_install" - Cap::SuSE::CFEngineInstall + Cap::SUSE::CFEngineInstall end provisioner(:cfengine) do diff --git a/plugins/provisioners/chef/cap/debian/chef_install.rb b/plugins/provisioners/chef/cap/debian/chef_install.rb new file mode 100644 index 000000000..3d9302d80 --- /dev/null +++ b/plugins/provisioners/chef/cap/debian/chef_install.rb @@ -0,0 +1,19 @@ +require_relative "../../omnibus" + +module VagrantPlugins + module Chef + module Cap + module Debian + module ChefInstall + def self.chef_install(machine, version, prerelease, download_path) + machine.communicate.sudo("apt-get update -y -qq") + machine.communicate.sudo("apt-get install -y -qq curl") + + command = Omnibus.build_command(version, prerelease, download_path) + machine.communicate.sudo(command) + end + end + end + end + end +end diff --git a/plugins/provisioners/chef/cap/linux/chef_installed.rb b/plugins/provisioners/chef/cap/linux/chef_installed.rb new file mode 100644 index 000000000..aa891c541 --- /dev/null +++ b/plugins/provisioners/chef/cap/linux/chef_installed.rb @@ -0,0 +1,22 @@ +module VagrantPlugins + module Chef + module Cap + module Linux + module ChefInstalled + # Check if Chef is installed at the given version. + # @return [true, false] + def self.chef_installed(machine, version) + knife = "/opt/chef/bin/knife" + command = "test -x #{knife}" + + if version != :latest + command << "&& #{knife} --version | grep 'Chef: #{version}'" + end + + machine.communicate.test(command, sudo: true) + end + end + end + end + end +end diff --git a/plugins/provisioners/chef/cap/omnios/chef_install.rb b/plugins/provisioners/chef/cap/omnios/chef_install.rb new file mode 100644 index 000000000..b20332638 --- /dev/null +++ b/plugins/provisioners/chef/cap/omnios/chef_install.rb @@ -0,0 +1,20 @@ +require_relative "../../omnibus" + +module VagrantPlugins + module Chef + module Cap + module OmniOS + module ChefInstall + def self.chef_install(machine, version, prerelease, download_path) + su_cmd = machine.config.solaris.suexec_cmd + + machine.communicate.execute("#{su_cmd} pkg list --no-refresh web/curl > /dev/null 2>&1 || pkg install -q --accept web/curl") + + command = VagrantPlugins::Chef::Omnibus.build_command(version, prerelease, download_path) + machine.communicate.execute(su_cmd + ' ' + command) + end + end + end + end + end +end diff --git a/plugins/provisioners/chef/cap/omnios/chef_installed.rb b/plugins/provisioners/chef/cap/omnios/chef_installed.rb new file mode 100644 index 000000000..2345d3b6a --- /dev/null +++ b/plugins/provisioners/chef/cap/omnios/chef_installed.rb @@ -0,0 +1,23 @@ +module VagrantPlugins + module Chef + module Cap + module OmniOS + module ChefInstalled + # TODO: this is the same code as cap/linux/chef_installed, consider merging + # Check if Chef is installed at the given version. + # @return [true, false] + def self.chef_installed(machine, version) + knife = "/opt/chef/bin/knife" + command = "test -x #{knife}" + + if version != :latest + command << "&& #{knife} --version | grep 'Chef: #{version}'" + end + + machine.communicate.test(command, sudo: true) + end + end + end + end + end +end diff --git a/plugins/provisioners/chef/cap/redhat/chef_install.rb b/plugins/provisioners/chef/cap/redhat/chef_install.rb new file mode 100644 index 000000000..c6aff7bad --- /dev/null +++ b/plugins/provisioners/chef/cap/redhat/chef_install.rb @@ -0,0 +1,18 @@ +require_relative "../../omnibus" + +module VagrantPlugins + module Chef + module Cap + module Redhat + module ChefInstall + def self.chef_install(machine, version, prerelease, download_path) + machine.communicate.sudo("yum install -y -q curl") + + command = Omnibus.build_command(version, prerelease, download_path) + machine.communicate.sudo(command) + end + end + end + end + end +end diff --git a/plugins/provisioners/chef/cap/windows/chef_installed.rb b/plugins/provisioners/chef/cap/windows/chef_installed.rb new file mode 100644 index 000000000..c95ea8498 --- /dev/null +++ b/plugins/provisioners/chef/cap/windows/chef_installed.rb @@ -0,0 +1,20 @@ +module VagrantPlugins + module Chef + module Cap + module Windows + module ChefInstalled + # Check if Chef is installed at the given version. + # @return [true, false] + def self.chef_installed(machine, version) + if version != :latest + command = 'if ((&knife --version) -Match "Chef: "' + version + '"){ exit 0 } else { exit 1 }' + else + command = 'if ((&knife --version) -Match "Chef: *"){ exit 0 } else { exit 1 }' + end + machine.communicate.test(command, sudo: true) + end + end + end + end + end +end diff --git a/plugins/provisioners/chef/command_builder.rb b/plugins/provisioners/chef/command_builder.rb index 844d48864..ad5093831 100644 --- a/plugins/provisioners/chef/command_builder.rb +++ b/plugins/provisioners/chef/command_builder.rb @@ -1,52 +1,78 @@ module VagrantPlugins module Chef class CommandBuilder - def initialize(config, client_type, is_windows=false, is_ui_colored=false) - @client_type = client_type - @config = config - @is_windows = is_windows - @is_ui_colored = is_ui_colored + def self.command(type, config, options = {}) + new(type, config, options).command + end - if client_type != :solo && client_type != :client - raise 'Invalid client_type, expected solo or client' + attr_reader :type + attr_reader :config + attr_reader :options + + def initialize(type, config, options = {}) + @type = type + @config = config + @options = options.dup + + if type != :client && type != :solo + raise "Unknown type `#{type.inspect}'!" end end - def build_command - "#{command_env}#{chef_binary_path} #{chef_arguments}" + def command + if config.binary_env + "#{config.binary_env} #{binary_path} #{args}" + else + "#{binary_path} #{args}" + end end protected - def command_env - @config.binary_env ? "#{@config.binary_env} " : "" - end + def binary_path + binary_path = "chef-#{type}" - def chef_binary_path - binary_path = "chef-#{@client_type}" - if @config.binary_path - binary_path = guest_friendly_path(File.join(@config.binary_path, binary_path)) + if config.binary_path + binary_path = File.join(config.binary_path, binary_path) + if windows? + binary_path = windows_friendly_path(binary_path) + end end + binary_path end - def chef_arguments - chef_arguments = "-c #{provisioning_path("#{@client_type}.rb")}" - chef_arguments << " -j #{provisioning_path("dna.json")}" - chef_arguments << " #{@config.arguments}" if @config.arguments - chef_arguments << " --no-color" unless @is_ui_colored - chef_arguments.strip + def args + args = "" + args << " --config #{provisioning_path("#{type}.rb")}" + args << " --json-attributes #{provisioning_path("dna.json")}" + args << " --local-mode" if options[:local_mode] + args << " --log_level #{config.log_level}" if config.log_level + args << " --force-formatter" + args << " --no-color" if !options[:colored] + args << " #{config.arguments.strip}" if config.arguments + + args.strip end def provisioning_path(file) - guest_friendly_path(File.join(@config.provisioning_path, file)) + if windows? + path = config.provisioning_path || "C:/vagrant-chef" + return windows_friendly_path(File.join(path, file)) + else + path = config.provisioning_path || "/tmp/vagrant-chef" + return File.join(path, file) + end end - def guest_friendly_path(path) - return path unless @is_windows - path.gsub!("/", "\\") + def windows_friendly_path(path) + path = path.gsub("/", "\\") path = "c:#{path}" if path.start_with?("\\") - path + return path + end + + def windows? + !!options[:windows] end end end diff --git a/plugins/provisioners/chef/config/base.rb b/plugins/provisioners/chef/config/base.rb index ae63a4522..370736fd6 100644 --- a/plugins/provisioners/chef/config/base.rb +++ b/plugins/provisioners/chef/config/base.rb @@ -1,139 +1,109 @@ -require "vagrant/util/counter" - module VagrantPlugins module Chef module Config class Base < Vagrant.plugin("2", :config) - extend Vagrant::Util::Counter - - attr_accessor :arguments - attr_accessor :attempts + # The path to Chef's bin/ directory. + # @return [String] attr_accessor :binary_path + + # Arbitrary environment variables to set before running the Chef + # provisioner command. + # @return [String] attr_accessor :binary_env - attr_accessor :custom_config_path - attr_accessor :encrypted_data_bag_secret_key_path - attr_accessor :environment - attr_accessor :formatter - attr_accessor :http_proxy - attr_accessor :http_proxy_user - attr_accessor :http_proxy_pass - attr_accessor :https_proxy - attr_accessor :https_proxy_user - attr_accessor :https_proxy_pass - attr_accessor :json + + # Install Chef on the system if it does not exist. Default is true. + # This is a trinary attribute (it can have three values): + # + # - true (bool) install Chef + # - false (bool) do not install Chef + # - "force" (string) install Chef, even if it is already installed at + # the proper version + # + # @return [true, false, String] + attr_accessor :install + + # The Chef log level. See the Chef docs for acceptable values. + # @return [String, Symbol] attr_accessor :log_level - attr_accessor :no_proxy - attr_accessor :node_name - attr_accessor :provisioning_path - attr_accessor :run_list - attr_accessor :file_cache_path - attr_accessor :file_backup_path - attr_accessor :verbose_logging + + # Install a prerelease version of Chef. + # @return [true, false] + attr_accessor :prerelease + + # The version of Chef to install. If Chef is already installed on the + # system, the installed version is compared with the requested version. + # If they match, no action is taken. If they do not match, version of + # the value specified in this attribute will be installed over top of + # the existing version (a warning will be displayed). + # + # You can also specify "latest" (default), which will install the latest + # version of Chef on the system. In this case, Chef will use whatever + # version is on the system. To force the newest version of Chef to be + # installed on every provision, set the {#install} option to "force". + # + # @return [String] + attr_accessor :version + + # The path where the Chef installer will be downloaded to. Only valid if + # install is true or "force". It defaults to nil, which means that the + # omnibus installer will choose the destination and you have no control + # over it. + # + # @return [String] + attr_accessor :installer_download_path def initialize super - @arguments = UNSET_VALUE - @attempts = UNSET_VALUE - @binary_path = UNSET_VALUE - @binary_env = UNSET_VALUE - @custom_config_path = UNSET_VALUE - @encrypted_data_bag_secret_key_path = UNSET_VALUE - @environment = UNSET_VALUE - @formatter = UNSET_VALUE - @http_proxy = UNSET_VALUE - @http_proxy_user = UNSET_VALUE - @http_proxy_pass = UNSET_VALUE - @https_proxy = UNSET_VALUE - @https_proxy_user = UNSET_VALUE - @https_proxy_pass = UNSET_VALUE - @log_level = UNSET_VALUE - @no_proxy = UNSET_VALUE - @node_name = UNSET_VALUE - @provisioning_path = UNSET_VALUE - @file_cache_path = UNSET_VALUE - @file_backup_path = UNSET_VALUE - @verbose_logging = UNSET_VALUE - - @json = {} - @run_list = [] - end - - def encrypted_data_bag_secret=(value) - puts "DEPRECATION: Chef encrypted_data_bag_secret has no effect anymore." - puts "Remove this from your Vagrantfile since it'll be removed in the next" - puts "Vagrant version." + @binary_path = UNSET_VALUE + @binary_env = UNSET_VALUE + @install = UNSET_VALUE + @log_level = UNSET_VALUE + @prerelease = UNSET_VALUE + @version = UNSET_VALUE + @installer_download_path = UNSET_VALUE end def finalize! - @arguments = nil if @arguments == UNSET_VALUE - @attempts = 1 if @attempts == UNSET_VALUE - @binary_path = nil if @binary_path == UNSET_VALUE - @binary_env = nil if @binary_env == UNSET_VALUE - @custom_config_path = nil if @custom_config_path == UNSET_VALUE - @environment = nil if @environment == UNSET_VALUE - @formatter = nil if @formatter == UNSET_VALUE - @http_proxy = nil if @http_proxy == UNSET_VALUE - @http_proxy_user = nil if @http_proxy_user == UNSET_VALUE - @http_proxy_pass = nil if @http_proxy_pass == UNSET_VALUE - @https_proxy = nil if @https_proxy == UNSET_VALUE - @https_proxy_user = nil if @https_proxy_user == UNSET_VALUE - @https_proxy_pass = nil if @https_proxy_pass == UNSET_VALUE - @log_level = :info if @log_level == UNSET_VALUE - @no_proxy = nil if @no_proxy == UNSET_VALUE - @node_name = nil if @node_name == UNSET_VALUE - @provisioning_path = nil if @provisioning_path == UNSET_VALUE - @file_backup_path = "/var/chef/backup" if @file_backup_path == UNSET_VALUE - @file_cache_path = "/var/chef/cache" if @file_cache_path == UNSET_VALUE - @verbose_logging = false if @verbose_logging == UNSET_VALUE + @binary_path = nil if @binary_path == UNSET_VALUE + @binary_env = nil if @binary_env == UNSET_VALUE + @install = true if @install == UNSET_VALUE + @log_level = :info if @log_level == UNSET_VALUE + @prerelease = false if @prerelease == UNSET_VALUE + @version = :latest if @version == UNSET_VALUE + @installer_download_path = nil if @installer_download_path == UNSET_VALUE - if @encrypted_data_bag_secret_key_path == UNSET_VALUE - @encrypted_data_bag_secret_key_path = nil + # Make sure the install is a symbol if it's not a boolean + if @install.respond_to?(:to_sym) + @install = @install.to_sym + end + + # Make sure the version is a symbol if it's not a boolean + if @version.respond_to?(:to_sym) + @version = @version.to_sym end # Make sure the log level is a symbol @log_level = @log_level.to_sym - - # Set the default provisioning path to be a unique path in /tmp - if !@provisioning_path - counter = self.class.get_and_update_counter(:chef_config) - @provisioning_path = "/tmp/vagrant-chef-#{counter}" - end end - def merge(other) - super.tap do |result| - result.instance_variable_set(:@json, @json.merge(other.json)) - result.instance_variable_set(:@run_list, (@run_list + other.run_list)) - end - end - - # Just like the normal configuration "validate" method except that - # it returns an array of errors that should be merged into some - # other error accumulator. + # Like validate, but returns a list of errors to append. + # + # @return [Array] def validate_base(machine) errors = _detected_errors - if @custom_config_path - expanded = File.expand_path(@custom_config_path, machine.env.root_path) - if !File.file?(expanded) - errors << I18n.t("vagrant.config.chef.custom_config_path_missing") - end + if missing?(log_level) + errors << I18n.t("vagrant.provisioners.chef.log_level_empty") end errors end - # Adds a recipe to the run list - def add_recipe(name) - name = "recipe[#{name}]" unless name =~ /^recipe\[(.+?)\]$/ - run_list << name - end - - # Adds a role to the run list - def add_role(name) - name = "role[#{name}]" unless name =~ /^role\[(.+?)\]$/ - run_list << name + # Determine if the given string is "missing" (blank) + # @return [true, false] + def missing?(obj) + obj.to_s.strip.empty? end end end diff --git a/plugins/provisioners/chef/config/base_runner.rb b/plugins/provisioners/chef/config/base_runner.rb new file mode 100644 index 000000000..cfee8e76e --- /dev/null +++ b/plugins/provisioners/chef/config/base_runner.rb @@ -0,0 +1,134 @@ +require "vagrant/util/counter" + +require_relative "base" + +module VagrantPlugins + module Chef + module Config + # This is the config base for Chef provisioners that need a full Chef + # Runner object, like chef-solo or chef-client. For provisioners like + # chef-apply, these options are not valid + class BaseRunner < Base + attr_accessor :arguments + attr_accessor :attempts + attr_accessor :custom_config_path + attr_accessor :encrypted_data_bag_secret_key_path + attr_accessor :environment + attr_accessor :formatter + attr_accessor :http_proxy + attr_accessor :http_proxy_user + attr_accessor :http_proxy_pass + attr_accessor :https_proxy + attr_accessor :https_proxy_user + attr_accessor :https_proxy_pass + attr_accessor :json + attr_accessor :no_proxy + attr_accessor :node_name + attr_accessor :provisioning_path + attr_accessor :run_list + attr_accessor :file_cache_path + attr_accessor :file_backup_path + attr_accessor :verbose_logging + attr_accessor :enable_reporting + + def initialize + super + + @arguments = UNSET_VALUE + @attempts = UNSET_VALUE + @custom_config_path = UNSET_VALUE + + # /etc/chef/client.rb config options + @encrypted_data_bag_secret_key_path = UNSET_VALUE + @environment = UNSET_VALUE + @formatter = UNSET_VALUE + @http_proxy = UNSET_VALUE + @http_proxy_user = UNSET_VALUE + @http_proxy_pass = UNSET_VALUE + @https_proxy = UNSET_VALUE + @https_proxy_user = UNSET_VALUE + @https_proxy_pass = UNSET_VALUE + @no_proxy = UNSET_VALUE + @node_name = UNSET_VALUE + @provisioning_path = UNSET_VALUE + @file_cache_path = UNSET_VALUE + @file_backup_path = UNSET_VALUE + @verbose_logging = UNSET_VALUE + @enable_reporting = UNSET_VALUE + + # Runner options + @json = {} + @run_list = [] + end + + def encrypted_data_bag_secret=(value) + puts "DEPRECATION: Chef encrypted_data_bag_secret has no effect anymore." + puts "Remove this from your Vagrantfile since it'll be removed in the next" + puts "Vagrant version." + end + + def finalize! + super + + @arguments = nil if @arguments == UNSET_VALUE + @attempts = 1 if @attempts == UNSET_VALUE + @custom_config_path = nil if @custom_config_path == UNSET_VALUE + @environment = nil if @environment == UNSET_VALUE + @formatter = nil if @formatter == UNSET_VALUE + @http_proxy = nil if @http_proxy == UNSET_VALUE + @http_proxy_user = nil if @http_proxy_user == UNSET_VALUE + @http_proxy_pass = nil if @http_proxy_pass == UNSET_VALUE + @https_proxy = nil if @https_proxy == UNSET_VALUE + @https_proxy_user = nil if @https_proxy_user == UNSET_VALUE + @https_proxy_pass = nil if @https_proxy_pass == UNSET_VALUE + @no_proxy = nil if @no_proxy == UNSET_VALUE + @node_name = nil if @node_name == UNSET_VALUE + @provisioning_path = nil if @provisioning_path == UNSET_VALUE + @file_backup_path = nil if @file_backup_path == UNSET_VALUE + @file_cache_path = nil if @file_cache_path == UNSET_VALUE + @verbose_logging = false if @verbose_logging == UNSET_VALUE + @enable_reporting = true if @enable_reporting == UNSET_VALUE + + if @encrypted_data_bag_secret_key_path == UNSET_VALUE + @encrypted_data_bag_secret_key_path = nil + end + end + + def merge(other) + super.tap do |result| + result.instance_variable_set(:@json, @json.merge(other.json)) + result.instance_variable_set(:@run_list, (@run_list + other.run_list)) + end + end + + # Just like the normal configuration "validate" method except that + # it returns an array of errors that should be merged into some + # other error accumulator. + def validate_base(machine) + errors = super + + if @custom_config_path + expanded = File.expand_path(@custom_config_path, machine.env.root_path) + if !File.file?(expanded) + errors << I18n.t("vagrant.config.chef.custom_config_path_missing") + end + end + + errors + end + + # Adds a recipe to the run list + def add_recipe(name) + name = "recipe[#{name}]" unless name =~ /^recipe\[(.+?)\]$/ + run_list << name + end + + # Adds a role to the run list + def add_role(name) + name = "role[#{name}]" unless name =~ /^role\[(.+?)\]$/ + run_list << name + end + end + end + end +end diff --git a/plugins/provisioners/chef/config/chef_apply.rb b/plugins/provisioners/chef/config/chef_apply.rb new file mode 100644 index 000000000..8f7081875 --- /dev/null +++ b/plugins/provisioners/chef/config/chef_apply.rb @@ -0,0 +1,46 @@ +require_relative "base" + +module VagrantPlugins + module Chef + module Config + class ChefApply < Base + # The raw recipe text (as a string) to execute via chef-apply. + # @return [String] + attr_accessor :recipe + + # The path (on the guest) where the uploaded apply recipe should be + # written (/tmp/vagrant-chef-apply-#.rb). + # @return [String] + attr_accessor :upload_path + + def initialize + super + + @recipe = UNSET_VALUE + @upload_path = UNSET_VALUE + end + + def finalize! + super + + @recipe = nil if @recipe == UNSET_VALUE + @upload_path = "/tmp/vagrant-chef-apply" if @upload_path == UNSET_VALUE + end + + def validate(machine) + errors = validate_base(machine) + + if missing?(recipe) + errors << I18n.t("vagrant.provisioners.chef.recipe_empty") + end + + if missing?(upload_path) + errors << I18n.t("vagrant.provisioners.chef.upload_path_empty") + end + + { "chef apply provisioner" => errors } + end + end + end + end +end diff --git a/plugins/provisioners/chef/config/chef_client.rb b/plugins/provisioners/chef/config/chef_client.rb index 9684d2b0e..331cc9b21 100644 --- a/plugins/provisioners/chef/config/chef_client.rb +++ b/plugins/provisioners/chef/config/chef_client.rb @@ -1,47 +1,67 @@ -require File.expand_path("../base", __FILE__) - require "vagrant/util/which" +require_relative "base_runner" + module VagrantPlugins module Chef module Config - class ChefClient < Base + class ChefClient < BaseRunner + # The URL endpoint to the Chef Server. + # @return [String] attr_accessor :chef_server_url + + # The path on disk to the Chef client key, + # @return [String] attr_accessor :client_key_path + + # Delete the client key when the VM is destroyed. Default is false. + # @return [true, false] attr_accessor :delete_client + + # Delete the node when the VM is destroyed. Default is false. + # @return [true, false] attr_accessor :delete_node + + # The path to the validation key on disk. + # @return [String] attr_accessor :validation_key_path + + # The name of the validation client. + # @return [String] attr_accessor :validation_client_name def initialize super - @chef_server_url = UNSET_VALUE - @client_key_path = UNSET_VALUE - @delete_client = UNSET_VALUE - @delete_node = UNSET_VALUE - @validation_key_path = UNSET_VALUE - @validation_client_name = UNSET_VALUE + @chef_server_url = UNSET_VALUE + @client_key_path = UNSET_VALUE + @delete_client = UNSET_VALUE + @delete_node = UNSET_VALUE + @validation_key_path = UNSET_VALUE + @validation_client_name = UNSET_VALUE end def finalize! super - @chef_server_url = nil if @chef_server_url == UNSET_VALUE - @client_key_path = "/etc/chef/client.pem" if @client_key_path == UNSET_VALUE - @delete_client = false if @delete_client == UNSET_VALUE - @delete_node = false if @delete_node == UNSET_VALUE + @chef_server_url = nil if @chef_server_url == UNSET_VALUE + @client_key_path = nil if @client_key_path == UNSET_VALUE + @delete_client = false if @delete_client == UNSET_VALUE + @delete_node = false if @delete_node == UNSET_VALUE @validation_client_name = "chef-validator" if @validation_client_name == UNSET_VALUE - @validation_key_path = nil if @validation_key_path == UNSET_VALUE + @validation_key_path = nil if @validation_key_path == UNSET_VALUE end def validate(machine) - errors = _detected_errors - errors.concat(validate_base(machine)) - errors << I18n.t("vagrant.config.chef.server_url_empty") if \ - !chef_server_url || chef_server_url.strip == "" - errors << I18n.t("vagrant.config.chef.validation_key_path") if \ - !validation_key_path + errors = validate_base(machine) + + if chef_server_url.to_s.strip.empty? + errors << I18n.t("vagrant.config.chef.server_url_empty") + end + + if validation_key_path.to_s.strip.empty? + errors << I18n.t("vagrant.config.chef.validation_key_path") + end if delete_client || delete_node if !Vagrant::Util::Which.which("knife") diff --git a/plugins/provisioners/chef/config/chef_solo.rb b/plugins/provisioners/chef/config/chef_solo.rb index 59c3b858e..ee4a2ac2e 100644 --- a/plugins/provisioners/chef/config/chef_solo.rb +++ b/plugins/provisioners/chef/config/chef_solo.rb @@ -1,25 +1,60 @@ -require File.expand_path("../base", __FILE__) +require_relative "base_runner" module VagrantPlugins module Chef module Config - class ChefSolo < Base + class ChefSolo < BaseRunner + # The path on disk where Chef cookbooks are stored. + # Default is "cookbooks". + # @return [String] attr_accessor :cookbooks_path + + # The path where data bags are stored on disk. + # @return [String] attr_accessor :data_bags_path + + # The path where environments are stored on disk. + # @return [String] attr_accessor :environments_path + + # A URL download a remote recipe from. Note: you should use chef-apply + # instead. + # + # @deprecated + # + # @return [String] attr_accessor :recipe_url + + # The path where roles are stored on disk. + # @return [String] attr_accessor :roles_path + + # The type of synced folders to use. + # @return [String] attr_accessor :synced_folder_type def initialize super - @cookbooks_path = UNSET_VALUE - @data_bags_path = UNSET_VALUE - @environments_path = UNSET_VALUE - @recipe_url = UNSET_VALUE - @roles_path = UNSET_VALUE - @synced_folder_type = UNSET_VALUE + @cookbooks_path = UNSET_VALUE + @data_bags_path = UNSET_VALUE + @environments_path = UNSET_VALUE + @recipe_url = UNSET_VALUE + @roles_path = UNSET_VALUE + @synced_folder_type = UNSET_VALUE + end + + # @deprecated This is deprecated in Chef and will be removed in Chef 12. + def recipe_url=(value) + puts "DEPRECATION: The 'recipe_url' setting for the Chef Solo" + puts "provisioner is deprecated. This value will be removed in" + puts "Chef 12. It is recommended you use the Chef Apply provisioner" + puts "instead. The 'recipe_url' setting will be removed in the next" + puts "version of Vagrant." + + if value + @recipe_url = value + end end def nfs=(value) @@ -63,12 +98,15 @@ module VagrantPlugins end def validate(machine) - errors = _detected_errors - errors.concat(validate_base(machine)) - errors << I18n.t("vagrant.config.chef.cookbooks_path_empty") if \ - !cookbooks_path || [cookbooks_path].flatten.empty? - errors << I18n.t("vagrant.config.chef.environment_path_required") if \ - environment && environments_path.empty? + errors = validate_base(machine) + + if [cookbooks_path].flatten.compact.empty? + errors << I18n.t("vagrant.config.chef.cookbooks_path_empty") + end + + if environment && environments_path.empty? + errors << I18n.t("vagrant.config.chef.environment_path_required") + end environments_path.each do |type, raw_path| next if type != :host @@ -76,7 +114,8 @@ module VagrantPlugins path = Pathname.new(raw_path).expand_path(machine.env.root_path) if !path.directory? errors << I18n.t("vagrant.config.chef.environment_path_missing", - path: raw_path.to_s) + path: raw_path.to_s + ) end end @@ -93,6 +132,8 @@ module VagrantPlugins # Make sure the path is an array config = [config] if !config.is_a?(Array) || config.first.is_a?(Symbol) + return [] if config.flatten.compact.empty? + # Make sure all the paths are in the proper format config.map do |path| path = [:host, path] if !path.is_a?(Array) diff --git a/plugins/provisioners/chef/config/chef_zero.rb b/plugins/provisioners/chef/config/chef_zero.rb new file mode 100644 index 000000000..d28de3dc9 --- /dev/null +++ b/plugins/provisioners/chef/config/chef_zero.rb @@ -0,0 +1,108 @@ +require_relative "chef_solo" + +module VagrantPlugins + module Chef + module Config + class ChefZero < BaseRunner + # The path on disk where Chef cookbooks are stored. + # Default is "cookbooks". + # @return [String] + attr_accessor :cookbooks_path + + # The path where data bags are stored on disk. + # @return [String] + attr_accessor :data_bags_path + + # The path where environments are stored on disk. + # @return [String] + attr_accessor :environments_path + + # The path where roles are stored on disk. + # @return [String] + attr_accessor :roles_path + + # The type of synced folders to use. + # @return [String] + attr_accessor :synced_folder_type + + def initialize + super + + @cookbooks_path = UNSET_VALUE + @data_bags_path = UNSET_VALUE + @environments_path = UNSET_VALUE + @roles_path = UNSET_VALUE + @synced_folder_type = UNSET_VALUE + end + + def finalize! + super + + @synced_folder_type = nil if @synced_folder_type == UNSET_VALUE + + if @cookbooks_path == UNSET_VALUE + @cookbooks_path = [] + @cookbooks_path << [:host, "cookbooks"] if !@recipe_url + @cookbooks_path << [:vm, "cookbooks"] + end + + @data_bags_path = [] if @data_bags_path == UNSET_VALUE + @roles_path = [] if @roles_path == UNSET_VALUE + @environments_path = [] if @environments_path == UNSET_VALUE + @environments_path = [@environments_path].flatten + + # Make sure the path is an array. + @cookbooks_path = prepare_folders_config(@cookbooks_path) + @data_bags_path = prepare_folders_config(@data_bags_path) + @roles_path = prepare_folders_config(@roles_path) + @environments_path = prepare_folders_config(@environments_path) + + end + + def validate(machine) + errors = validate_base(machine) + + if [cookbooks_path].flatten.compact.empty? + errors << I18n.t("vagrant.config.chef.cookbooks_path_empty") + end + + if environment && environments_path.empty? + errors << I18n.t("vagrant.config.chef.environment_path_required") + end + + environments_path.each do |type, raw_path| + next if type != :host + + path = Pathname.new(raw_path).expand_path(machine.env.root_path) + if !path.directory? + errors << I18n.t("vagrant.config.chef.environment_path_missing", + path: raw_path.to_s + ) + end + end + + { "chef zero provisioner" => errors } + end + + protected + + # This takes any of the configurations that take a path or + # array of paths and turns it into the proper format. + # + # @return [Array] + def prepare_folders_config(config) + # Make sure the path is an array + config = [config] if !config.is_a?(Array) || config.first.is_a?(Symbol) + + return [] if config.flatten.compact.empty? + + # Make sure all the paths are in the proper format + config.map do |path| + path = [:host, path] if !path.is_a?(Array) + path + end + end + end + end + end +end diff --git a/plugins/provisioners/chef/installer.rb b/plugins/provisioners/chef/installer.rb new file mode 100644 index 000000000..16467111c --- /dev/null +++ b/plugins/provisioners/chef/installer.rb @@ -0,0 +1,46 @@ +module VagrantPlugins + module Chef + class Installer + def initialize(machine, options = {}) + @machine = machine + @version = options.fetch(:version, :latest) + @prerelease = options.fetch(:prerelease, :latest) + @force = options.fetch(:force, false) + @download_path = options.fetch(:download_path, nil) + end + + # This handles verifying the Chef installation, installing it if it was + # requested, and so on. This method will raise exceptions if things are + # wrong. + def ensure_installed + # If the guest cannot check if Chef is installed, just exit printing a + # warning... + if !@machine.guest.capability?(:chef_installed) + @machine.ui.warn(I18n.t("vagrant.chef_cant_detect")) + return + end + + if !should_install_chef? + @machine.ui.info(I18n.t("vagrant.chef_already_installed", + version: @version.to_s)) + return + end + + @machine.ui.detail(I18n.t("vagrant.chef_installing", + version: @version.to_s)) + @machine.guest.capability(:chef_install, @version, @prerelease, @download_path) + + if !@machine.guest.capability(:chef_installed, @version) + raise Provisioner::Base::ChefError, :install_failed + end + end + + # Determine if Chef should be installed. Chef is installed if the "force" + # option is given or if the guest does not have Chef installed at the + # proper version. + def should_install_chef? + @force || !@machine.guest.capability(:chef_installed, @version) + end + end + end +end diff --git a/plugins/provisioners/chef/omnibus.rb b/plugins/provisioners/chef/omnibus.rb new file mode 100644 index 000000000..5c2eef2fb --- /dev/null +++ b/plugins/provisioners/chef/omnibus.rb @@ -0,0 +1,32 @@ +module VagrantPlugins + module Chef + module Omnibus + OMNITRUCK = "https://www.chef.io/chef/install.sh".freeze + + # Read more about the Omnibus installer here: + # https://docs.getchef.com/install_omnibus.html + def build_command(version, prerelease = false, download_path = nil) + command = "curl -sL #{OMNITRUCK} | sudo bash" + + if prerelease || version != :latest || download_path != nil + command << " -s --" + end + + if prerelease + command << " -p" + end + + if version != :latest + command << " -v \"#{version}\"" + end + + if download_path + command << " -d \"#{download_path}\"" + end + + command + end + module_function :build_command + end + end +end diff --git a/plugins/provisioners/chef/plugin.rb b/plugins/provisioners/chef/plugin.rb index afe068047..540429fad 100644 --- a/plugins/provisioners/chef/plugin.rb +++ b/plugins/provisioners/chef/plugin.rb @@ -2,37 +2,87 @@ require "pathname" require "vagrant" +require_relative "command_builder" + module VagrantPlugins module Chef - root = Pathname.new(File.expand_path("../", __FILE__)) - autoload :CommandBuilder, root.join("command_builder") - class Plugin < Vagrant.plugin("2") name "chef" description <<-DESC Provides support for provisioning your virtual machines with - Chef via `chef-solo` or `chef-client`. + Chef via `chef-solo`, `chef-client`, `chef-zero` or `chef-apply`. DESC - config(:chef_solo, :provisioner) do - require File.expand_path("../config/chef_solo", __FILE__) - Config::ChefSolo + config(:chef_apply, :provisioner) do + require_relative "config/chef_apply" + Config::ChefApply end config(:chef_client, :provisioner) do - require File.expand_path("../config/chef_client", __FILE__) + require_relative "config/chef_client" Config::ChefClient end - provisioner(:chef_solo) do - require File.expand_path("../provisioner/chef_solo", __FILE__) - Provisioner::ChefSolo + config(:chef_solo, :provisioner) do + require_relative "config/chef_solo" + Config::ChefSolo + end + + config(:chef_zero, :provisioner) do + require_relative "config/chef_zero" + Config::ChefZero + end + + provisioner(:chef_apply) do + require_relative "provisioner/chef_apply" + Provisioner::ChefApply end provisioner(:chef_client) do - require File.expand_path("../provisioner/chef_client", __FILE__) + require_relative "provisioner/chef_client" Provisioner::ChefClient end + + provisioner(:chef_solo) do + require_relative "provisioner/chef_solo" + Provisioner::ChefSolo + end + + provisioner(:chef_zero) do + require_relative "provisioner/chef_zero" + Provisioner::ChefZero + end + + guest_capability(:linux, :chef_installed) do + require_relative "cap/linux/chef_installed" + Cap::Linux::ChefInstalled + end + + guest_capability(:windows, :chef_installed) do + require_relative "cap/windows/chef_installed" + Cap::Windows::ChefInstalled + end + + guest_capability(:debian, :chef_install) do + require_relative "cap/debian/chef_install" + Cap::Debian::ChefInstall + end + + guest_capability(:redhat, :chef_install) do + require_relative "cap/redhat/chef_install" + Cap::Redhat::ChefInstall + end + + guest_capability(:omnios, :chef_installed) do + require_relative "cap/omnios/chef_installed" + Cap::OmniOS::ChefInstalled + end + + guest_capability(:omnios, :chef_install) do + require_relative "cap/omnios/chef_install" + Cap::OmniOS::ChefInstall + end + end end end diff --git a/plugins/provisioners/chef/provisioner/base.rb b/plugins/provisioners/chef/provisioner/base.rb index a891b1251..e0b2b3fc4 100644 --- a/plugins/provisioners/chef/provisioner/base.rb +++ b/plugins/provisioners/chef/provisioner/base.rb @@ -2,6 +2,8 @@ require 'tempfile' require "vagrant/util/template_renderer" +require_relative "../installer" + module VagrantPlugins module Chef module Provisioner @@ -13,21 +15,40 @@ module VagrantPlugins error_namespace("vagrant.provisioners.chef") end + def initialize(machine, config) + super + + @logger = Log4r::Logger.new("vagrant::provisioners::chef") + end + + def install_chef + return if !config.install + + @logger.info("Checking for Chef installation...") + installer = Installer.new(@machine, + force: config.install == :force, + version: config.version, + prerelease: config.prerelease, + download_path: config.installer_download_path + ) + installer.ensure_installed + end + def verify_binary(binary) # Checks for the existence of chef binary and error if it # doesn't exist. + if windows? + command = "if ((&'#{binary}' -v) -Match 'Chef: *'){ exit 0 } else { exit 1 }" + else + command = "sh -c 'command -v #{binary}'" + end + @machine.communicate.sudo( - "which #{binary}", + command, error_class: ChefError, error_key: :chef_not_detected, - binary: binary) - end - - # This returns the command to run Chef for the given client - # type. - def build_command(client) - builder = CommandBuilder.new(@config, client, windows?, @machine.env.ui.is_a?(Vagrant::UI::Colored)) - return builder.build_command + binary: binary, + ) end # Returns the path to the Chef binary, taking into account the @@ -38,14 +59,20 @@ module VagrantPlugins end def chown_provisioning_folder - paths = [@config.provisioning_path, - @config.file_backup_path, - @config.file_cache_path] + paths = [ + guest_provisioning_path, + guest_file_backup_path, + guest_file_cache_path, + ] @machine.communicate.tap do |comm| paths.each do |path| - comm.sudo("mkdir -p #{path}") - comm.sudo("chown -h #{@machine.ssh_info[:username]} #{path}") + if windows? + comm.sudo("mkdir ""#{path}"" -f") + else + comm.sudo("mkdir -p #{path}") + comm.sudo("chown -h #{@machine.ssh_info[:username]} #{path}") + end end end end @@ -57,7 +84,7 @@ module VagrantPlugins expanded = File.expand_path( @config.custom_config_path, @machine.env.root_path) remote_custom_config_path = File.join( - config.provisioning_path, "custom-config.rb") + guest_provisioning_path, "custom-config.rb") @machine.communicate.upload(expanded, remote_custom_config_path) end @@ -66,11 +93,12 @@ module VagrantPlugins custom_configuration: remote_custom_config_path, encrypted_data_bag_secret: guest_encrypted_data_bag_secret_key_path, environment: @config.environment, - file_cache_path: @config.file_cache_path, - file_backup_path: @config.file_backup_path, + file_cache_path: guest_file_cache_path, + file_backup_path: guest_file_backup_path, log_level: @config.log_level.to_sym, node_name: @config.node_name, verbose_logging: @config.verbose_logging, + enable_reporting: @config.enable_reporting, http_proxy: @config.http_proxy, http_proxy_user: @config.http_proxy_user, http_proxy_pass: @config.http_proxy_pass, @@ -87,7 +115,7 @@ module VagrantPlugins temp.write(config_file) temp.close - remote_file = File.join(config.provisioning_path, filename) + remote_file = File.join(guest_provisioning_path, filename) @machine.communicate.tap do |comm| comm.sudo("rm -f #{remote_file}", error_check: false) comm.upload(temp.path, remote_file) @@ -99,7 +127,8 @@ module VagrantPlugins # Get the JSON that we're going to expose to Chef json = @config.json - json[:run_list] = @config.run_list if !@config.run_list.empty? + json[:run_list] = @config.run_list if @config.run_list && + !@config.run_list.empty? json = JSON.pretty_generate(json) # Create a temporary file to store the data so we @@ -108,9 +137,15 @@ module VagrantPlugins temp.write(json) temp.close - remote_file = File.join(@config.provisioning_path, "dna.json") + remote_file = File.join(guest_provisioning_path, "dna.json") @machine.communicate.tap do |comm| - comm.sudo("rm -f #{remote_file}", error_check: false) + if windows? + command = "if (test-path '#{remote_file}') {rm '#{remote_file}' -force -recurse}" + else + command = "rm -f #{remote_file}" + end + + comm.sudo(command, error_check: false) comm.upload(temp.path, remote_file) end end @@ -123,7 +158,13 @@ module VagrantPlugins "vagrant.provisioners.chef.upload_encrypted_data_bag_secret_key") @machine.communicate.tap do |comm| - comm.sudo("rm -f #{remote_file}", error_check: false) + if windows? + command = "if (test-path ""#{remote_file}"") {rm ""#{remote_file}"" -force -recurse}" + else + command = "rm -f #{remote_file}" + end + + comm.sudo(command, error_check: false) comm.upload(encrypted_data_bag_secret_key_path, remote_file) end end @@ -131,7 +172,13 @@ module VagrantPlugins def delete_encrypted_data_bag_secret remote_file = guest_encrypted_data_bag_secret_key_path if remote_file - @machine.communicate.sudo("rm -f #{remote_file}", error_check: false) + if windows? + command = "if (test-path ""#{remote_file}"") {rm ""#{remote_file}"" -force -recurse}" + else + command = "rm -f #{remote_file}" + end + + @machine.communicate.sudo(command, error_check: false) end end @@ -142,7 +189,43 @@ module VagrantPlugins def guest_encrypted_data_bag_secret_key_path if @config.encrypted_data_bag_secret_key_path - File.join(@config.provisioning_path, "encrypted_data_bag_secret_key") + File.join(guest_provisioning_path, "encrypted_data_bag_secret_key") + end + end + + def guest_provisioning_path + if !@config.provisioning_path.nil? + return @config.provisioning_path + end + + if windows? + "C:/vagrant-chef" + else + "/tmp/vagrant-chef" + end + end + + def guest_file_backup_path + if !@config.file_backup_path.nil? + return @config.file_backup_path + end + + if windows? + "C:/chef/backup" + else + "/var/chef/backup" + end + end + + def guest_file_cache_path + if !@config.file_cache_path.nil? + return @config.file_cache_path + end + + if windows? + "C:/chef/cache" + else + "/var/chef/cache" end end diff --git a/plugins/provisioners/chef/provisioner/chef_apply.rb b/plugins/provisioners/chef/provisioner/chef_apply.rb new file mode 100644 index 000000000..602c81f3d --- /dev/null +++ b/plugins/provisioners/chef/provisioner/chef_apply.rb @@ -0,0 +1,72 @@ +require "digest/md5" +require "tempfile" + +require_relative "base" + +module VagrantPlugins + module Chef + module Provisioner + class ChefApply < Base + def provision + install_chef + verify_binary(chef_binary_path("chef-apply")) + + command = "chef-apply" + command << " \"#{target_recipe_path}\"" + command << " --log_level #{config.log_level}" + + user = @machine.ssh_info[:username] + + # Reset upload path permissions for the current ssh user + if windows? + @machine.communicate.sudo("mkdir ""#{config.upload_path}"" -f") + else + @machine.communicate.sudo("mkdir -p #{config.upload_path}") + @machine.communicate.sudo("chown -R #{user} #{config.upload_path}") + end + + # Upload the recipe + upload_recipe + + @machine.ui.info(I18n.t("vagrant.provisioners.chef.running_apply", + script: config.path) + ) + + # Execute it with sudo + @machine.communicate.sudo(command) do |type, data| + if [:stderr, :stdout].include?(type) + # Output the data with the proper color based on the stream. + color = (type == :stdout) ? :green : :red + + # Chomp the data to avoid the newlines that the Chef outputs + @machine.env.ui.info(data.chomp, color: color, prefix: false) + end + end + end + + # The destination (on the guest) where the recipe will live + # @return [String] + def target_recipe_path + key = Digest::MD5.hexdigest(config.recipe) + File.join(config.upload_path, "recipe-#{key}.rb") + end + + # Write the raw recipe contents to a tempfile and upload that to the + # machine. + def upload_recipe + # Write the raw recipe contents to a tempfile + file = Tempfile.new(["vagrant-chef-apply", ".rb"]) + file.write(config.recipe) + file.rewind + + # Upload the tempfile to the guest + @machine.communicate.upload(file.path, target_recipe_path) + ensure + # Delete our template + file.close + file.unlink + end + end + end + end +end diff --git a/plugins/provisioners/chef/provisioner/chef_client.rb b/plugins/provisioners/chef/provisioner/chef_client.rb index d91b3c22c..da44fce78 100644 --- a/plugins/provisioners/chef/provisioner/chef_client.rb +++ b/plugins/provisioners/chef/provisioner/chef_client.rb @@ -3,7 +3,7 @@ require 'pathname' require 'vagrant' require 'vagrant/util/subprocess' -require File.expand_path("../base", __FILE__) +require_relative "base" module VagrantPlugins module Chef @@ -18,6 +18,7 @@ module VagrantPlugins end def provision + install_chef verify_binary(chef_binary_path("chef-client")) chown_provisioning_folder create_client_key_folder @@ -36,9 +37,13 @@ module VagrantPlugins def create_client_key_folder @machine.ui.info I18n.t("vagrant.provisioners.chef.client_key_folder") - path = Pathname.new(@config.client_key_path) + path = Pathname.new(guest_client_key_path) - @machine.communicate.sudo("mkdir -p #{path.dirname}") + if windows? + @machine.communicate.sudo("mkdir ""#{path.dirname}"" -f") + else + @machine.communicate.sudo("mkdir -p #{path.dirname}") + end end def upload_validation_key @@ -51,7 +56,7 @@ module VagrantPlugins chef_server_url: @config.chef_server_url, validation_client_name: @config.validation_client_name, validation_key: guest_validation_key_path, - client_key: @config.client_key_path, + client_key: guest_client_key_path, }) end @@ -64,7 +69,10 @@ module VagrantPlugins @machine.guest.capability(:wait_for_reboot) end - command = build_command(:client) + command = CommandBuilder.command(:client, @config, + windows: windows?, + colored: @machine.env.ui.color?, + ) @config.attempts.times do |attempt| if attempt == 0 @@ -96,8 +104,20 @@ module VagrantPlugins File.expand_path(@config.validation_key_path, @machine.env.root_path) end + def guest_client_key_path + if !@config.client_key_path.nil? + return @config.client_key_path + end + + if windows? + "C:/chef/client.pem" + else + "/etc/chef/client.pem" + end + end + def guest_validation_key_path - File.join(@config.provisioning_path, "validation.pem") + File.join(guest_provisioning_path, "validation.pem") end def delete_from_chef_server(deletable) @@ -109,16 +129,14 @@ module VagrantPlugins # Knife is not part of the current Vagrant bundle, so it needs to run # in the context of the system. Vagrant.global_lock do - Bundler.with_clean_env do - command = ["knife", deletable, "delete", "--yes", node_name] - r = Vagrant::Util::Subprocess.execute(*command) - if r.exit_code != 0 - @machine.ui.error(I18n.t( - "vagrant.chef_client_cleanup_failed", - deletable: deletable, - stdout: r.stdout, - stderr: r.stderr)) - end + command = ["knife", deletable, "delete", "--yes", node_name] + r = Vagrant::Util::Subprocess.execute(*command) + if r.exit_code != 0 + @machine.ui.error(I18n.t( + "vagrant.chef_client_cleanup_failed", + deletable: deletable, + stdout: r.stdout, + stderr: r.stderr)) end end end diff --git a/plugins/provisioners/chef/provisioner/chef_solo.rb b/plugins/provisioners/chef/provisioner/chef_solo.rb index bf66c99d5..f3b52f178 100644 --- a/plugins/provisioners/chef/provisioner/chef_solo.rb +++ b/plugins/provisioners/chef/provisioner/chef_solo.rb @@ -1,8 +1,12 @@ +require "digest/md5" +require "securerandom" +require "set" + require "log4r" require "vagrant/util/counter" -require File.expand_path("../base", __FILE__) +require_relative "base" module VagrantPlugins module Chef @@ -11,6 +15,8 @@ module VagrantPlugins class ChefSolo < Base extend Vagrant::Util::Counter include Vagrant::Util::Counter + include Vagrant::Action::Builtin::MixinSyncedFolders + attr_reader :environments_folders attr_reader :cookbook_folders attr_reader :role_folders @@ -19,6 +25,7 @@ module VagrantPlugins def initialize(machine, config) super @logger = Log4r::Logger.new("vagrant::provisioners::chef_solo") + @shared_folders = [] end def configure(root_config) @@ -27,22 +34,22 @@ module VagrantPlugins @data_bags_folders = expanded_folders(@config.data_bags_path, "data_bags") @environments_folders = expanded_folders(@config.environments_path, "environments") - share_folders(root_config, "csc", @cookbook_folders) - share_folders(root_config, "csr", @role_folders) - share_folders(root_config, "csdb", @data_bags_folders) - share_folders(root_config, "cse", @environments_folders) + existing = synced_folders(@machine, cached: true) + share_folders(root_config, "csc", @cookbook_folders, existing) + share_folders(root_config, "csr", @role_folders, existing) + share_folders(root_config, "csdb", @data_bags_folders, existing) + share_folders(root_config, "cse", @environments_folders, existing) end def provision + install_chef # Verify that the proper shared folders exist. check = [] - [@cookbook_folders, @role_folders, @data_bags_folders, @environments_folders].each do |folders| - folders.each do |type, local_path, remote_path| - # We only care about checking folders that have a local path, meaning - # they were shared from the local machine, rather than assumed to - # exist on the VM. - check << remote_path if local_path - end + @shared_folders.each do |type, local_path, remote_path| + # We only care about checking folders that have a local path, meaning + # they were shared from the local machine, rather than assumed to + # exist on the VM. + check << remote_path if local_path end chown_provisioning_folder @@ -72,8 +79,10 @@ module VagrantPlugins local_path = File.expand_path(path, @machine.env.root_path) if File.exist?(local_path) - # Path exists on the host, setup the remote path - remote_path = "#{@config.provisioning_path}/chef-solo-#{get_and_update_counter(:cookbooks_path)}" + # Path exists on the host, setup the remote path. We use + # the MD5 of the local path so that it is predictable. + key = Digest::MD5.hexdigest(local_path) + remote_path = "#{guest_provisioning_path}/#{key}" else @machine.ui.warn(I18n.t("vagrant.provisioners.chef.cookbook_folder_not_found_warning", path: local_path.to_s)) @@ -82,7 +91,7 @@ module VagrantPlugins else # Path already exists on the virtual machine. Expand it # relative to where we're provisioning. - remote_path = File.expand_path(path, @config.provisioning_path) + remote_path = File.expand_path(path, guest_provisioning_path) # Remove drive letter if running on a windows host. This is a bit # of a hack but is the most portable way I can think of at the moment @@ -103,30 +112,56 @@ module VagrantPlugins # Shares the given folders with the given prefix. The folders should # be of the structure resulting from the `expanded_folders` function. - def share_folders(root_config, prefix, folders) - folders.each do |type, local_path, remote_path| - if type == :host - opts = {} - opts[:id] = "v-#{prefix}-#{self.class.get_and_update_counter(:shared_folder)}" - opts[:type] = @config.synced_folder_type if @config.synced_folder_type - - root_config.vm.synced_folder(local_path, remote_path, opts) + def share_folders(root_config, prefix, folders, existing=nil) + existing_set = Set.new + (existing || []).each do |_, fs| + fs.each do |id, data| + existing_set.add(data[:guestpath]) end end + + folders.each do |type, local_path, remote_path| + next if type != :host + + # If this folder already exists, then we don't share it, it means + # it was already put down on disk. + # + # NOTE: This is currently commented out because it was causing + # major bugs (GH-5199). We will investigate why this is in more + # detail for 1.8.0, but we wanted to fix this in a patch release + # and this was the hammer that did that. +=begin + if existing_set.include?(remote_path) + @logger.debug("Not sharing #{local_path}, exists as #{remote_path}") + next + end +=end + + key = Digest::MD5.hexdigest(remote_path) + key = key[0..8] + + opts = {} + opts[:id] = "v-#{prefix}-#{key}" + opts[:type] = @config.synced_folder_type if @config.synced_folder_type + + root_config.vm.synced_folder(local_path, remote_path, opts) + end + + @shared_folders += folders end def setup_solo_config - cookbooks_path = guest_paths(@cookbook_folders) - roles_path = guest_paths(@role_folders) - data_bags_path = guest_paths(@data_bags_folders).first - environments_path = guest_paths(@environments_folders).first - setup_config("provisioners/chef_solo/solo", "solo.rb", { - cookbooks_path: cookbooks_path, + setup_config("provisioners/chef_solo/solo", "solo.rb", solo_config) + end + + def solo_config + { + cookbooks_path: guest_paths(@cookbook_folders), recipe_url: @config.recipe_url, - roles_path: roles_path, - data_bags_path: data_bags_path, - environments_path: environments_path, - }) + roles_path: guest_paths(@role_folders), + data_bags_path: guest_paths(@data_bags_folders).first, + environments_path: guest_paths(@environments_folders).first + } end def run_chef_solo @@ -138,7 +173,10 @@ module VagrantPlugins @machine.guest.capability(:wait_for_reboot) end - command = build_command(:solo) + command = CommandBuilder.command(:solo, @config, + windows: windows?, + colored: @machine.env.ui.color?, + ) @config.attempts.times do |attempt| if attempt == 0 diff --git a/plugins/provisioners/chef/provisioner/chef_zero.rb b/plugins/provisioners/chef/provisioner/chef_zero.rb new file mode 100644 index 000000000..48078a09f --- /dev/null +++ b/plugins/provisioners/chef/provisioner/chef_zero.rb @@ -0,0 +1,112 @@ +require "digest/md5" +require "securerandom" +require "set" + +require "log4r" + +require "vagrant/util/counter" + +require_relative "chef_solo" + +module VagrantPlugins + module Chef + module Provisioner + # This class implements provisioning via chef-zero. + class ChefZero < ChefSolo + def initialize(machine, config) + super + @logger = Log4r::Logger.new("vagrant::provisioners::chef_zero") + end + + def provision + install_chef + # Verify that the proper shared folders exist. + check = [] + @shared_folders.each do |type, local_path, remote_path| + # We only care about checking folders that have a local path, meaning + # they were shared from the local machine, rather than assumed to + # exist on the VM. + check << remote_path if local_path + end + + chown_provisioning_folder + verify_shared_folders(check) + verify_binary(chef_binary_path("chef-client")) + upload_encrypted_data_bag_secret + setup_json + setup_zero_config + run_chef_zero + delete_encrypted_data_bag_secret + end + + def setup_zero_config + setup_config("provisioners/chef_zero/zero", "client.rb", { + local_mode: true, + enable_reporting: false, + cookbooks_path: guest_paths(@cookbook_folders), + roles_path: guest_paths(@role_folders), + data_bags_path: guest_paths(@data_bags_folders).first, + environments_path: guest_paths(@environments_folders).first, + }) + end + + def run_chef_zero + if @config.run_list && @config.run_list.empty? + @machine.ui.warn(I18n.t("vagrant.chef_run_list_empty")) + end + + if @machine.guest.capability?(:wait_for_reboot) + @machine.guest.capability(:wait_for_reboot) + end + + command = CommandBuilder.command(:client, @config, + windows: windows?, + colored: @machine.env.ui.color?, + local_mode: true, + ) + + @config.attempts.times do |attempt| + if attempt == 0 + @machine.ui.info I18n.t("vagrant.provisioners.chef.running_zero") + else + @machine.ui.info I18n.t("vagrant.provisioners.chef.running_zero_again") + end + + opts = { error_check: false, elevated: true } + exit_status = @machine.communicate.sudo(command, opts) do |type, data| + # Output the data with the proper color based on the stream. + color = type == :stdout ? :green : :red + + data = data.chomp + next if data.empty? + + @machine.ui.info(data, color: color) + end + + # There is no need to run Chef again if it converges + return if exit_status == 0 + end + + # If we reached this point then Chef never converged! Error. + raise ChefError, :no_convergence + end + + def verify_shared_folders(folders) + folders.each do |folder| + @logger.debug("Checking for shared folder: #{folder}") + if !@machine.communicate.test("test -d #{folder}", sudo: true) + raise ChefError, :missing_shared_folders + end + end + end + + protected + + # Extracts only the remote paths from a list of folders + def guest_paths(folders) + folders.map { |parts| parts[2] } + end + end + end + end +end diff --git a/plugins/provisioners/docker/cap/debian/docker_configure_auto_start.rb b/plugins/provisioners/docker/cap/debian/docker_configure_auto_start.rb deleted file mode 100644 index 1c29d7d6c..000000000 --- a/plugins/provisioners/docker/cap/debian/docker_configure_auto_start.rb +++ /dev/null @@ -1,33 +0,0 @@ -module VagrantPlugins - module DockerProvisioner - module Cap - module Debian - module DockerConfigureAutoStart - def self.docker_configure_auto_start(machine) - machine.communicate.tap do |comm| - if !comm.test('grep -q \'\-r=true\' /etc/default/docker') - comm.sudo("echo 'DOCKER_OPTS=\"-r=true ${DOCKER_OPTS}\"' >> /etc/default/docker") - comm.sudo("stop docker") - comm.sudo("start docker") - - # Wait some amount time for the pid to become available - # so that we don't start executing Docker commands until - # it is available. - if machine.guest.capability?(:docker_daemon_running) - [0, 1, 2, 4].each do |delay| - sleep delay - break if machine.guest.capability(:docker_daemon_running) - end - else - # This OS doesn't support checking if Docker is running, - # so just wait 5 seconds. - sleep 5 - end - end - end - end - end - end - end - end -end diff --git a/plugins/provisioners/docker/cap/debian/docker_install.rb b/plugins/provisioners/docker/cap/debian/docker_install.rb index 0d376a243..b598ee785 100644 --- a/plugins/provisioners/docker/cap/debian/docker_install.rb +++ b/plugins/provisioners/docker/cap/debian/docker_install.rb @@ -8,17 +8,20 @@ module VagrantPlugins package << "-#{version}" if version != :latest machine.communicate.tap do |comm| + comm.sudo("apt-get update -y") # TODO: Perform check on the host machine if aufs is installed and using LXC if machine.provider_name != :lxc comm.sudo("lsmod | grep aufs || modprobe aufs || apt-get install -y linux-image-extra-`uname -r`") end - comm.sudo("apt-get update -y") comm.sudo("apt-get install -y --force-yes -q curl") - comm.sudo("curl http://get.docker.io/gpg | apt-key add -") - comm.sudo("echo deb http://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list") + comm.sudo("curl -sSL https://get.docker.com/gpg | apt-key add -") + comm.sudo("echo deb https://get.docker.com/ubuntu docker main > /etc/apt/sources.list.d/docker.list") comm.sudo("apt-get update") comm.sudo("echo lxc lxc/directory string /var/lib/lxc | debconf-set-selections") comm.sudo("apt-get install -y --force-yes -q xz-utils #{package} -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold'") + + # chmod the directory if it exists + comm.sudo("chmod 0755 /var/lib/docker") end end end diff --git a/plugins/provisioners/docker/cap/linux/docker_configure_vagrant_user.rb b/plugins/provisioners/docker/cap/linux/docker_configure_vagrant_user.rb index 7a3d87e94..5283a9ef1 100644 --- a/plugins/provisioners/docker/cap/linux/docker_configure_vagrant_user.rb +++ b/plugins/provisioners/docker/cap/linux/docker_configure_vagrant_user.rb @@ -7,7 +7,7 @@ module VagrantPlugins ssh_info = machine.ssh_info machine.communicate.tap do |comm| - if !comm.test("id -Gn | grep docker") + if comm.test("getent group docker") && !comm.test("id -Gn | grep docker") comm.sudo("usermod -a -G docker #{ssh_info[:username]}") end end diff --git a/plugins/provisioners/docker/cap/linux/docker_installed.rb b/plugins/provisioners/docker/cap/linux/docker_installed.rb index f1d05fa2f..11acdac5a 100644 --- a/plugins/provisioners/docker/cap/linux/docker_installed.rb +++ b/plugins/provisioners/docker/cap/linux/docker_installed.rb @@ -4,7 +4,19 @@ module VagrantPlugins module Linux module DockerInstalled def self.docker_installed(machine) - machine.communicate.test("test -f /usr/bin/docker", sudo: true) + paths = [ + "/usr/bin/docker", + "/usr/local/bin/docker", + "/usr/sbin/docker", + ] + + paths.each do |p| + if machine.communicate.test("test -f #{p}", sudo: true) + return true + end + end + + return false end end end diff --git a/plugins/provisioners/docker/cap/redhat/docker_configure_auto_start.rb b/plugins/provisioners/docker/cap/redhat/docker_configure_auto_start.rb deleted file mode 100644 index aaae9d787..000000000 --- a/plugins/provisioners/docker/cap/redhat/docker_configure_auto_start.rb +++ /dev/null @@ -1,15 +0,0 @@ -module VagrantPlugins - module DockerProvisioner - module Cap - module Redhat - module DockerConfigureAutoStart - def self.docker_configure_auto_start(machine) - if ! machine.communicate.test('grep -q \'\-r=true\' /etc/sysconfig/docker') - machine.communicate.sudo("sed -i.bak 's/docker -d/docker -d -r=true/' /etc/sysconfig/docker ") - end - end - end - end - end - end -end diff --git a/plugins/provisioners/docker/client.rb b/plugins/provisioners/docker/client.rb index 6ea699056..391c76395 100644 --- a/plugins/provisioners/docker/client.rb +++ b/plugins/provisioners/docker/client.rb @@ -59,7 +59,15 @@ module VagrantPlugins id = "$(cat #{config[:cidfile]})" if container_exists?(id) - start_container(id) + if container_args_changed?(config) + @machine.ui.info(I18n.t("vagrant.docker_restarting_container", + name: config[:name], + )) + stop_container(id) + create_container(config) + else + start_container(id) + end else create_container(config) end @@ -75,26 +83,41 @@ module VagrantPlugins end end + def stop_container(id) + @machine.communicate.sudo %[ + docker stop #{id} + docker rm #{id} + ] + end + def container_running?(id) lookup_container(id) end + def container_args_changed?(config) + path = container_data_path(config) + return true if !path.exist? + + args = container_run_args(config) + sha = Digest::SHA1.hexdigest(args) + return true if path.read.chomp != sha + + return false + end + def create_container(config) - name = config[:name] + args = container_run_args(config) - # If the name is the automatically assigned name, then - # replace the "/" with "-" because "/" is not a valid - # character for a docker container name. - name = name.gsub("/", "-") if name == config[:original_name] - - args = "--cidfile=#{config[:cidfile]} " - args << "-d " if config[:daemonize] - args << "--name #{name} " if name && config[:auto_assign_name] - args << config[:args] if config[:args] @machine.communicate.sudo %[ rm -f #{config[:cidfile]} - docker run #{args} #{config[:image]} #{config[:cmd]} + docker run #{args} ] + + name = container_name(config) + sha = Digest::SHA1.hexdigest(args) + container_data_path(config).open("w+") do |f| + f.write(sha) + end end def lookup_container(id, list_all = false) @@ -105,18 +128,42 @@ module VagrantPlugins # recent versions use the full container ID # See https://github.com/dotcloud/docker/pull/2140 for more information return comm.test("#{docker_ps} | grep -wFq #{id}") || - comm.test("#{docker_ps} -notrunc | grep -wFq #{id}") + comm.test("#{docker_ps} -notrunc | grep -wFq #{id}") end end - + + def container_name(config) + name = config[:name] + + # If the name is the automatically assigned name, then + # replace the "/" with "-" because "/" is not a valid + # character for a docker container name. + name = name.gsub("/", "-") if name == config[:original_name] + name + end + + def container_run_args(config) + name = container_name(config) + + args = "--cidfile=#{config[:cidfile]} " + args << "-d " if config[:daemonize] + args << "--name #{name} " if name && config[:auto_assign_name] + args << "--restart=#{config[:restart]}" if config[:restart] + args << " #{config[:args]}" if config[:args] + + "#{args} #{config[:image]} #{config[:cmd]}".strip + end + + def container_data_path(config) + name = container_name(config) + @machine.data_dir.join("docker-#{name}") + end + protected # This handles outputting the communication data back to the UI def handle_comm(type, data) if [:stderr, :stdout].include?(type) - # Output the data with the proper color based on the stream. - color = type == :stdout ? :green : :red - # Clear out the newline since we add one data = data.chomp return if data.empty? @@ -127,7 +174,6 @@ module VagrantPlugins @machine.ui.info(data.chomp, options) end end - end end end diff --git a/plugins/provisioners/docker/config.rb b/plugins/provisioners/docker/config.rb index c96e97305..bcf4cf672 100644 --- a/plugins/provisioners/docker/config.rb +++ b/plugins/provisioners/docker/config.rb @@ -70,8 +70,9 @@ module VagrantPlugins @__containers.each do |name, params| params[:image] ||= name - params[:auto_assign_name] = true if !params.has_key?(:auto_assign_name) - params[:daemonize] = true if !params.has_key?(:daemonize) + params[:auto_assign_name] = true if !params.key?(:auto_assign_name) + params[:daemonize] = true if !params.key?(:daemonize) + params[:restart] = "always" if !params.key?(:restart) end end end diff --git a/plugins/provisioners/docker/installer.rb b/plugins/provisioners/docker/installer.rb index 01028f5c0..cfcebdd43 100644 --- a/plugins/provisioners/docker/installer.rb +++ b/plugins/provisioners/docker/installer.rb @@ -24,13 +24,6 @@ module VagrantPlugins end end - if @machine.guest.capability?(:docker_configure_auto_start) - @machine.ui.detail(I18n.t("vagrant.docker_configure_autostart")) - @machine.guest.capability(:docker_configure_auto_start) - else - @machine.env.ui.warn I18n.t('vagrant.docker_auto_start_not_available') - end - if @machine.guest.capability?(:docker_configure_vagrant_user) @machine.guest.capability(:docker_configure_vagrant_user) end diff --git a/plugins/provisioners/docker/plugin.rb b/plugins/provisioners/docker/plugin.rb index c68e772e1..b606729ac 100644 --- a/plugins/provisioners/docker/plugin.rb +++ b/plugins/provisioners/docker/plugin.rb @@ -19,11 +19,6 @@ module VagrantPlugins Cap::Debian::DockerInstall end - guest_capability("debian", "docker_configure_auto_start") do - require_relative "cap/debian/docker_configure_auto_start" - Cap::Debian::DockerConfigureAutoStart - end - guest_capability("debian", "docker_start_service") do require_relative "cap/debian/docker_start_service" Cap::Debian::DockerStartService @@ -34,11 +29,6 @@ module VagrantPlugins Cap::Redhat::DockerInstall end - guest_capability("redhat", "docker_configure_auto_start") do - require_relative "cap/redhat/docker_configure_auto_start" - Cap::Redhat::DockerConfigureAutoStart - end - guest_capability("redhat", "docker_start_service") do require_relative "cap/redhat/docker_start_service" Cap::Redhat::DockerStartService diff --git a/plugins/provisioners/file/config.rb b/plugins/provisioners/file/config.rb index 2af27e67b..e8e2d6c92 100644 --- a/plugins/provisioners/file/config.rb +++ b/plugins/provisioners/file/config.rb @@ -1,3 +1,5 @@ +require "pathname" + require "vagrant" module VagrantPlugins @@ -15,10 +17,10 @@ module VagrantPlugins errors << I18n.t("vagrant.provisioners.file.no_dest_file") end if source - s = File.expand_path(source) - if ! File.exist?(s) + s = Pathname.new(source).expand_path(machine.env.root_path) + if !s.exist? errors << I18n.t("vagrant.provisioners.file.path_invalid", - path: s) + path: s.to_s) end end diff --git a/plugins/provisioners/puppet/config/puppet.rb b/plugins/provisioners/puppet/config/puppet.rb index 3117f9f6f..9910c6e05 100644 --- a/plugins/provisioners/puppet/config/puppet.rb +++ b/plugins/provisioners/puppet/config/puppet.rb @@ -1,33 +1,40 @@ -require "vagrant/util/counter" - module VagrantPlugins module Puppet module Config class Puppet < Vagrant.plugin("2", :config) - extend Vagrant::Util::Counter + + # The path to Puppet's bin/ directory. + # @return [String] + attr_accessor :binary_path attr_accessor :facter attr_accessor :hiera_config_path attr_accessor :manifest_file attr_accessor :manifests_path + attr_accessor :environment + attr_accessor :environment_path attr_accessor :module_path attr_accessor :options attr_accessor :synced_folder_type + attr_accessor :synced_folder_args attr_accessor :temp_dir attr_accessor :working_directory def initialize super - @hiera_config_path = UNSET_VALUE - @manifest_file = UNSET_VALUE - @manifests_path = UNSET_VALUE - @module_path = UNSET_VALUE - @options = [] - @facter = {} + @binary_path = UNSET_VALUE + @hiera_config_path = UNSET_VALUE + @manifest_file = UNSET_VALUE + @manifests_path = UNSET_VALUE + @environment = UNSET_VALUE + @environment_path = UNSET_VALUE + @module_path = UNSET_VALUE + @options = [] + @facter = {} @synced_folder_type = UNSET_VALUE - @temp_dir = UNSET_VALUE - @working_directory = UNSET_VALUE + @temp_dir = UNSET_VALUE + @working_directory = UNSET_VALUE end def nfs=(value) @@ -45,35 +52,49 @@ module VagrantPlugins def merge(other) super.tap do |result| result.facter = @facter.merge(other.facter) + result.environment_path = @facter.merge(other.environment_path) + result.environment = @facter.merge(other.environment) end end def finalize! super - if @manifests_path == UNSET_VALUE + if @environment_path == UNSET_VALUE && @manifests_path == UNSET_VALUE + #If both are unset, assume 'manifests' mode for now. TBD: Switch to environments by default? @manifests_path = [:host, "manifests"] end - if @manifests_path && !@manifests_path.is_a?(Array) + # If the paths are just strings, assume they are 'host' paths (rather than guest) + if @environment_path != UNSET_VALUE && !@environment_path.is_a?(Array) + @environment_path = [:host, @environment_path] + end + if @manifests_path != UNSET_VALUE && !@manifests_path.is_a?(Array) @manifests_path = [:host, @manifests_path] end - - @manifests_path[0] = @manifests_path[0].to_sym - @hiera_config_path = nil if @hiera_config_path == UNSET_VALUE - @manifest_file = "default.pp" if @manifest_file == UNSET_VALUE - @module_path = nil if @module_path == UNSET_VALUE - @synced_folder_type = nil if @synced_folder_type == UNSET_VALUE - @temp_dir = nil if @temp_dir == UNSET_VALUE - @working_directory = nil if @working_directory == UNSET_VALUE - # Set a default temp dir that has an increasing counter so - # that multiple Puppet definitions won't overwrite each other - if !@temp_dir - counter = self.class.get_and_update_counter(:puppet_config) - @temp_dir = "/tmp/vagrant-puppet-#{counter}" + if @environment_path == UNSET_VALUE + @manifests_path[0] = @manifests_path[0].to_sym + @environment_path = nil + @manifest_file = "default.pp" if @manifest_file == UNSET_VALUE + else + @environment_path[0] = @environment_path[0].to_sym + @environment = "production" if @environment == UNSET_VALUE + if @manifests_path == UNSET_VALUE + @manifests_path = nil + end + if @manifest_file == UNSET_VALUE + @manifest_file = nil + end end + + @binary_path = nil if @binary_path == UNSET_VALUE + @module_path = nil if @module_path == UNSET_VALUE + @synced_folder_type = nil if @synced_folder_type == UNSET_VALUE + @synced_folder_args = nil if @synced_folder_args == UNSET_VALUE + @temp_dir = "/tmp/vagrant-puppet" if @temp_dir == UNSET_VALUE + @working_directory = nil if @working_directory == UNSET_VALUE end # Returns the module paths as an array of paths expanded relative to the @@ -97,7 +118,7 @@ module VagrantPlugins this_expanded_module_paths = expanded_module_paths(machine.env.root_path) # Manifests path/file validation - if manifests_path[0].to_sym == :host + if manifests_path != nil && manifests_path[0].to_sym == :host expanded_path = Pathname.new(manifests_path[1]). expand_path(machine.env.root_path) if !expanded_path.directory? @@ -110,6 +131,25 @@ module VagrantPlugins manifest: expanded_manifest_file.to_s) end end + elsif environment_path != nil && environment_path[0].to_sym == :host + # Environments path/file validation + expanded_path = Pathname.new(environment_path[1]). + expand_path(machine.env.root_path) + if !expanded_path.directory? + errors << I18n.t("vagrant.provisioners.puppet.environment_path_missing", + path: expanded_path.to_s) + else + expanded_environment_file = expanded_path.join(environment) + if !expanded_environment_file.file? && !expanded_environment_file.directory? + errors << I18n.t("vagrant.provisioners.puppet.environment_missing", + environment: environment.to_s, + environment_path: expanded_path.to_s) + end + end + end + + if environment_path == nil && manifests_path == nil + errors << "Please specify either a Puppet environment_path + environment (preferred) or manifests_path (deprecated)." end # Module paths validation @@ -119,7 +159,6 @@ module VagrantPlugins path: path) end end - { "puppet provisioner" => errors } end end diff --git a/plugins/provisioners/puppet/plugin.rb b/plugins/provisioners/puppet/plugin.rb index 362a1c421..4d2b74bbe 100644 --- a/plugins/provisioners/puppet/plugin.rb +++ b/plugins/provisioners/puppet/plugin.rb @@ -10,22 +10,22 @@ module VagrantPlugins DESC config(:puppet, :provisioner) do - require File.expand_path("../config/puppet", __FILE__) + require_relative "config/puppet" Config::Puppet end config(:puppet_server, :provisioner) do - require File.expand_path("../config/puppet_server", __FILE__) + require_relative "config/puppet_server" Config::PuppetServer end provisioner(:puppet) do - require File.expand_path("../provisioner/puppet", __FILE__) + require_relative "provisioner/puppet" Provisioner::Puppet end provisioner(:puppet_server) do - require File.expand_path("../provisioner/puppet_server", __FILE__) + require_relative "provisioner/puppet_server" Provisioner::PuppetServer end end diff --git a/plugins/provisioners/puppet/provisioner/puppet.rb b/plugins/provisioners/puppet/provisioner/puppet.rb index f49316bf7..67954ca77 100644 --- a/plugins/provisioners/puppet/provisioner/puppet.rb +++ b/plugins/provisioners/puppet/provisioner/puppet.rb @@ -1,3 +1,5 @@ +require "digest/md5" + require "log4r" module VagrantPlugins @@ -18,23 +20,36 @@ module VagrantPlugins # Calculate the paths we're going to use based on the environment root_path = @machine.env.root_path @expanded_module_paths = @config.expanded_module_paths(root_path) - @manifest_file = File.join(manifests_guest_path, @config.manifest_file) # Setup the module paths @module_paths = [] - @expanded_module_paths.each_with_index do |path, i| - @module_paths << [path, File.join(config.temp_dir, "modules-#{i}")] + @expanded_module_paths.each_with_index do |path, _| + key = Digest::MD5.hexdigest(path.to_s) + @module_paths << [path, File.join(config.temp_dir, "modules-#{key}")] end folder_opts = {} folder_opts[:type] = @config.synced_folder_type if @config.synced_folder_type folder_opts[:owner] = "root" if !@config.synced_folder_type + folder_opts[:args] = @config.synced_folder_args if @config.synced_folder_args + folder_opts[:nfs__quiet] = true - # Share the manifests directory with the guest - if @config.manifests_path[0].to_sym == :host - root_config.vm.synced_folder( - File.expand_path(@config.manifests_path[1], root_path), - manifests_guest_path, folder_opts) + if @config.environment_path.is_a?(Array) + # Share the environments directory with the guest + if @config.environment_path[0].to_sym == :host + root_config.vm.synced_folder( + File.expand_path(@config.environment_path[1], root_path), + environments_guest_path, folder_opts) + end + end + if @config.manifest_file + @manifest_file = File.join(manifests_guest_path, @config.manifest_file) + # Share the manifests directory with the guest + if @config.manifests_path[0].to_sym == :host + root_config.vm.synced_folder( + File.expand_path(@config.manifests_path[1], root_path), + manifests_guest_path, folder_opts) + end end # Share the module paths @@ -43,6 +58,24 @@ module VagrantPlugins end end + def parse_environment_metadata + # Parse out the environment manifest path since puppet apply doesnt do that for us. + environment_conf = File.join(environments_guest_path, @config.environment, "environment.conf") + if @machine.communicate.test("test -e #{environment_conf}", sudo: true) + conf = @machine.communicate.sudo("cat #{environment_conf}") do | type, data| + if type == :stdout + data.each_line do |line| + if line =~ /^\s*manifest\s+=\s+([^\s]+)/ + @manifest_file = $1 + @manifest_file.gsub! '$basemodulepath:', "#{environments_guest_path}/#{@config.environment}/" + @logger.debug("Using manifest from environment.conf: #{@manifest_file}") + end + end + end + end + end + end + def provision # If the machine has a wait for reboot functionality, then # do that (primarily Windows) @@ -50,11 +83,19 @@ module VagrantPlugins @machine.guest.capability(:wait_for_reboot) end + # In environment mode we still need to specify a manifest file, if its not, use the one from env config if specified. + if !@manifest_file + @manifest_file = "#{environments_guest_path}/#{@config.environment}/manifests/site.pp" + parse_environment_metadata + end # Check that the shared folders are properly shared check = [] - if @config.manifests_path[0] == :host + if @config.manifests_path.is_a?(Array) && @config.manifests_path[0] == :host check << manifests_guest_path end + if @config.environment_path.is_a?(Array) && @config.environment_path[0] == :host + check << environments_guest_path + end @module_paths.each do |host_path, guest_path| check << guest_path end @@ -68,7 +109,7 @@ module VagrantPlugins verify_shared_folders(check) # Verify Puppet is installed and run it - verify_binary("puppet") + verify_binary(puppet_binary_path("puppet")) # Upload Hiera configuration if we have it @hiera_config_path = nil @@ -85,19 +126,40 @@ module VagrantPlugins def manifests_guest_path if config.manifests_path[0] == :host # The path is on the host, so point to where it is shared - File.join(config.temp_dir, "manifests") + key = Digest::MD5.hexdigest(config.manifests_path[1]) + File.join(config.temp_dir, "manifests-#{key}") else # The path is on the VM, so just point directly to it config.manifests_path[1] end end + def environments_guest_path + if config.environment_path[0] == :host + # The path is on the host, so point to where it is shared + File.join(config.temp_dir, "environments") + else + # The path is on the VM, so just point directly to it + config.environment_path[1] + end + end + + # Returns the path to the Puppet binary, taking into account the + # `binary_path` configuration option. + def puppet_binary_path(binary) + return binary if !@config.binary_path + return File.join(@config.binary_path, binary) + end + def verify_binary(binary) - @machine.communicate.sudo( - "which #{binary}", - error_class: PuppetError, - error_key: :not_detected, - binary: binary) + if !machine.communicate.test("sh -c 'command -v #{binary}'") + @config.binary_path = "/opt/puppetlabs/bin/" + @machine.communicate.sudo( + "test -x /opt/puppetlabs/bin/#{binary}", + error_class: PuppetError, + error_key: :not_detected, + binary: binary) + end end def run_puppet_apply @@ -121,12 +183,18 @@ module VagrantPlugins options << "--hiera_config=#{@hiera_config_path}" end - if !@machine.env.ui.is_a?(Vagrant::UI::Colored) + if !@machine.env.ui.color? options << "--color=false" end - options << "--manifestdir #{manifests_guest_path}" options << "--detailed-exitcodes" + if config.environment_path + options << "--environmentpath #{environments_guest_path}/" + options << "--environment #{@config.environment}" + else + options << "--manifestdir #{manifests_guest_path}" + end + options << @manifest_file options = options.join(" ") @@ -146,7 +214,7 @@ module VagrantPlugins facter = "#{facts.join(" ")} " end - command = "#{facter}puppet apply #{options}" + command = "#{facter} #{config.binary_path}puppet apply #{options}" if config.working_directory if windows? command = "cd #{config.working_directory}; if (`$?) \{ #{command} \}" @@ -155,12 +223,19 @@ module VagrantPlugins end end - @machine.ui.info(I18n.t( - "vagrant.provisioners.puppet.running_puppet", - manifest: config.manifest_file)) + if config.environment_path + @machine.ui.info(I18n.t( + "vagrant.provisioners.puppet.running_puppet_env", + environment: config.environment)) + else + @machine.ui.info(I18n.t( + "vagrant.provisioners.puppet.running_puppet", + manifest: config.manifest_file)) + end opts = { elevated: true, + error_class: Vagrant::Errors::VagrantError, error_key: :ssh_bad_exit_status_muted, good_exit: [0,2], } diff --git a/plugins/provisioners/puppet/provisioner/puppet_server.rb b/plugins/provisioners/puppet/provisioner/puppet_server.rb index ea3a67308..9c9e9e6b7 100644 --- a/plugins/provisioners/puppet/provisioner/puppet_server.rb +++ b/plugins/provisioners/puppet/provisioner/puppet_server.rb @@ -68,7 +68,7 @@ module VagrantPlugins end # Disable colors if we must - if !@machine.env.ui.is_a?(Vagrant::UI::Colored) + if !@machine.env.ui.color? options << "--color=false" end diff --git a/plugins/provisioners/salt/bootstrap-salt.ps1 b/plugins/provisioners/salt/bootstrap-salt.ps1 index c8d7e7ab2..382e1ff81 100644 --- a/plugins/provisioners/salt/bootstrap-salt.ps1 +++ b/plugins/provisioners/salt/bootstrap-salt.ps1 @@ -1,11 +1,17 @@ -# Salt version to install -$version = '2014.1.10' +Param( + [string]$version +) + +# Salt version to install - default to latest if there is an issue +if ($version -notmatch "201[0-9]\.[0-9]\.[0-9](\-\d{1})?"){ + $version = '2015.5.2' +} # Create C:\tmp\ - if Vagrant doesn't upload keys and/or config it might not exist -New-Item C:\tmp\ -ItemType directory | out-null +New-Item C:\tmp\ -ItemType directory -force | out-null # Copy minion keys & config to correct location -New-Item C:\salt\conf\pki\minion\ -ItemType directory | out-null +New-Item C:\salt\conf\pki\minion\ -ItemType directory -force | out-null # Check if minion keys have been uploaded if (Test-Path C:\tmp\minion.pem) { @@ -13,20 +19,15 @@ if (Test-Path C:\tmp\minion.pem) { cp C:\tmp\minion.pub C:\salt\conf\pki\minion\ } -# Check if minion config has been uploaded -if (Test-Path C:\tmp\minion) { - cp C:\tmp\minion C:\salt\conf\ -} - # Detect architecture if ([IntPtr]::Size -eq 4) { - $arch = "win32" + $arch = "x86" } else { $arch = "AMD64" } # Download minion setup file -Write-Host "Downloading Salt minion installer ($arch)..." +Write-Host "Downloading Salt minion installer $version-$arch..." $webclient = New-Object System.Net.WebClient $url = "https://docs.saltstack.com/downloads/Salt-Minion-$version-$arch-Setup.exe" $file = "C:\tmp\salt.exe" @@ -34,7 +35,13 @@ $webclient.DownloadFile($url, $file) # Install minion silently Write-Host "Installing Salt minion..." -C:\tmp\salt.exe /S +#Wait for process to exit before continuing... +C:\tmp\salt.exe /S | Out-Null + +# Check if minion config has been uploaded +if (Test-Path C:\tmp\minion) { + cp C:\tmp\minion C:\salt\conf\ +} # Wait for salt-minion service to be registered before trying to start it $service = Get-Service salt-minion -ErrorAction SilentlyContinue @@ -63,4 +70,4 @@ if ($service.Status -eq "Stopped") { exit 1 } -Write-Host "Salt minion successfully installed" +Write-Host "Salt minion successfully installed" \ No newline at end of file diff --git a/plugins/provisioners/salt/config.rb b/plugins/provisioners/salt/config.rb index 0a99d3fc8..638a07bfb 100644 --- a/plugins/provisioners/salt/config.rb +++ b/plugins/provisioners/salt/config.rb @@ -12,15 +12,20 @@ module VagrantPlugins attr_accessor :master_config attr_accessor :master_key attr_accessor :master_pub + attr_accessor :grains_config attr_accessor :run_highstate attr_accessor :run_overstate + attr_accessor :orchestrations attr_accessor :always_install attr_accessor :bootstrap_script attr_accessor :verbose attr_accessor :seed_master + attr_accessor :config_dir attr_reader :pillar_data attr_accessor :colorize attr_accessor :log_level + attr_accessor :masterless + attr_accessor :minion_id ## bootstrap options attr_accessor :temp_config_dir @@ -30,6 +35,7 @@ module VagrantPlugins attr_accessor :install_syndic attr_accessor :no_minion attr_accessor :bootstrap_options + attr_accessor :version def initialize @minion_config = UNSET_VALUE @@ -38,6 +44,7 @@ module VagrantPlugins @master_config = UNSET_VALUE @master_key = UNSET_VALUE @master_pub = UNSET_VALUE + @grains_config = UNSET_VALUE @run_highstate = UNSET_VALUE @run_overstate = UNSET_VALUE @always_install = UNSET_VALUE @@ -54,6 +61,10 @@ module VagrantPlugins @install_syndic = UNSET_VALUE @no_minion = UNSET_VALUE @bootstrap_options = UNSET_VALUE + @config_dir = UNSET_VALUE + @masterless = UNSET_VALUE + @minion_id = UNSET_VALUE + @version = UNSET_VALUE end def finalize! @@ -63,6 +74,7 @@ module VagrantPlugins @master_config = nil if @master_config == UNSET_VALUE @master_key = nil if @master_key == UNSET_VALUE @master_pub = nil if @master_pub == UNSET_VALUE + @grains_config = nil if @grains_config == UNSET_VALUE @run_highstate = nil if @run_highstate == UNSET_VALUE @run_overstate = nil if @run_overstate == UNSET_VALUE @always_install = nil if @always_install == UNSET_VALUE @@ -79,7 +91,10 @@ module VagrantPlugins @install_syndic = nil if @install_syndic == UNSET_VALUE @no_minion = nil if @no_minion == UNSET_VALUE @bootstrap_options = nil if @bootstrap_options == UNSET_VALUE - + @config_dir = nil if @config_dir == UNSET_VALUE + @masterless = false if @masterless == UNSET_VALUE + @minion_id = nil if @minion_id == UNSET_VALUE + @version = nil if @version == UNSET_VALUE end def pillar(data) @@ -87,19 +102,30 @@ module VagrantPlugins @pillar_data = Vagrant::Util::DeepMerge.deep_merge(@pillar_data, data) end + def default_config_dir(machine) + guest_type = machine.config.vm.guest || :linux + + # FIXME: there should be a way to do that a bit smarter + if guest_type == :windows + return "C:\\salt" + else + return "/etc/salt" + end + end + def validate(machine) errors = _detected_errors if @minion_config expanded = Pathname.new(@minion_config).expand_path(machine.env.root_path) if !expanded.file? - errors << I18n.t("vagrant.provisioners.salt.minion_config_nonexist") + errors << I18n.t("vagrant.provisioners.salt.minion_config_nonexist", missing_config_file: expanded) end end if @master_config expanded = Pathname.new(@master_config).expand_path(machine.env.root_path) if !expanded.file? - errors << I18n.t("vagrant.provisioners.salt.master_config_nonexist") + errors << I18n.t("vagrant.provisioners.salt.master_config_nonexist", missing_config_file: expanded) end end @@ -115,14 +141,23 @@ module VagrantPlugins end end + if @grains_config + expanded = Pathname.new(@grains_config).expand_path(machine.env.root_path) + if !expanded.file? + errors << I18n.t("vagrant.provisioners.salt.grains_config_nonexist") + end + end + if @install_master && !@no_minion && !@seed_master && @run_highstate errors << I18n.t("vagrant.provisioners.salt.must_accept_keys") end + if @config_dir.nil? + @config_dir = default_config_dir(machine) + end + return {"salt provisioner" => errors} end - - end end end diff --git a/plugins/provisioners/salt/provisioner.rb b/plugins/provisioners/salt/provisioner.rb index 6225fd339..bf462df12 100644 --- a/plugins/provisioners/salt/provisioner.rb +++ b/plugins/provisioners/salt/provisioner.rb @@ -9,6 +9,7 @@ module VagrantPlugins run_bootstrap_script call_overstate call_highstate + call_orchestrate end # Return a list of accepted keys @@ -75,7 +76,7 @@ module VagrantPlugins end def need_configure - @config.minion_config or @config.minion_key or @config.master_config or @config.master_key + @config.minion_config or @config.minion_key or @config.master_config or @config.master_key or @config.grains_config or @config.version end def need_install @@ -142,6 +143,13 @@ module VagrantPlugins options = "%s %s" % [options, @config.install_args] end + if @config.install_command + # If this is defined, we will ignore both install_type and + # install_args and use this instead. Every necessary command option + # will need to be specified by the user. + options = @config.install_command + end + if @config.verbose @machine.env.ui.info "Using Bootstrap Options: %s" % options end @@ -181,6 +189,11 @@ module VagrantPlugins @machine.env.ui.info "Copying salt master config to vm." @machine.communicate.upload(expanded_path(@config.master_config).to_s, temp_config_dir + "/master") end + + if @config.grains_config + @machine.env.ui.info "Copying salt grains config to vm." + @machine.communicate.upload(expanded_path(@config.grains_config).to_s, temp_config_dir + "/grains") + end end # Copy master and minion keys to VM @@ -231,16 +244,21 @@ module VagrantPlugins bootstrap_path = get_bootstrap if @machine.config.vm.communicator == :winrm + if @config.version + options = "-version %s" % @config.version + end bootstrap_destination = File.join(config_dir, "bootstrap_salt.ps1") else bootstrap_destination = File.join(config_dir, "bootstrap_salt.sh") end - @machine.communicate.sudo("rm -f %s" % bootstrap_destination) + if @machine.communicate.test("test -f %s" % bootstrap_destination) + @machine.communicate.sudo("rm -f %s" % bootstrap_destination) + end @machine.communicate.upload(bootstrap_path.to_s, bootstrap_destination) @machine.communicate.sudo("chmod +x %s" % bootstrap_destination) if @machine.config.vm.communicator == :winrm - bootstrap = @machine.communicate.sudo("powershell.exe -executionpolicy bypass -file %s" % [bootstrap_destination]) do |type, data| + bootstrap = @machine.communicate.sudo("powershell.exe -executionpolicy bypass -file %s %s" % [bootstrap_destination, options]) do |type, data| if data[0] == "\n" # Remove any leading newline but not whitespace. If we wanted to # remove newlines and whitespace we would have used data.lstrip @@ -297,14 +315,35 @@ module VagrantPlugins end end + def call_masterless + @machine.env.ui.info "Calling state.highstate in local mode... (this may take a while)" + cmd = "salt-call state.highstate --local" + if @config.minion_id + cmd += " --id #{@config.minion_id}" + end + cmd += " -l debug#{get_pillar}" + @machine.communicate.sudo(cmd) do |type, data| + if @config.verbose + @machine.env.ui.info(data) + end + end + end + def call_highstate - if @config.run_highstate + if @config.minion_config + @machine.env.ui.info "Copying salt minion config to #{@config.config_dir}" + @machine.communicate.upload(expanded_path(@config.minion_config).to_s, @config.config_dir + "/minion") + end + + if @config.masterless + call_masterless + elsif @config.run_highstate @machine.env.ui.info "Calling state.highstate... (this may take a while)" if @config.install_master @machine.communicate.sudo("salt '*' saltutil.sync_all") - @machine.communicate.sudo("salt '*' state.highstate --retcode-passthrough --verbose#{get_loglevel}#{get_colorize}#{get_pillar}") do |type, data| + @machine.communicate.sudo("salt '*' state.highstate --verbose#{get_loglevel}#{get_colorize}#{get_pillar}") do |type, data| if @config.verbose - @machine.env.ui.info(data) + @machine.env.ui.info(data.rstrip) end end else @@ -313,14 +352,14 @@ module VagrantPlugins @machine.communicate.execute("C:\\salt\\salt-call.exe saltutil.sync_all", opts) @machine.communicate.execute("C:\\salt\\salt-call.exe state.highstate --retcode-passthrough #{get_loglevel}#{get_colorize}#{get_pillar}", opts) do |type, data| if @config.verbose - @machine.env.ui.info(data) + @machine.env.ui.info(data.rstrip) end end else @machine.communicate.sudo("salt-call saltutil.sync_all") @machine.communicate.sudo("salt-call state.highstate --retcode-passthrough #{get_loglevel}#{get_colorize}#{get_pillar}") do |type, data| if @config.verbose - @machine.env.ui.info(data) + @machine.env.ui.info(data.rstrip) end end end @@ -329,6 +368,34 @@ module VagrantPlugins @machine.env.ui.info "run_highstate set to false. Not running state.highstate." end end + + def call_orchestrate + if !@config.orchestrations + @machine.env.ui.info "orchestrate is nil. Not running state.orchestrate." + return + end + + if !@config.install_master + @machine.env.ui.info "orchestrate does not make sense on a minion. Not running state.orchestrate." + return + end + + log_output = lambda do |type, data| + if @config.verbose + @machine.env.ui.info(data) + end + end + + @machine.env.ui.info "Running the following orchestrations: #{@config.orchestrations}" + @machine.env.ui.info "Running saltutil.sync_all before orchestrating" + @machine.communicate.sudo("salt '*' saltutil.sync_all", &log_output) + + @config.orchestrations.each do |orchestration| + cmd = "salt-run -l info state.orchestrate #{orchestration}" + @machine.env.ui.info "Calling #{cmd}... (this may take a while)" + @machine.communicate.sudo(cmd, &log_output) + end + end end end end diff --git a/plugins/provisioners/shell/config.rb b/plugins/provisioners/shell/config.rb index fbb7c812e..2f4957214 100644 --- a/plugins/provisioners/shell/config.rb +++ b/plugins/provisioners/shell/config.rb @@ -10,6 +10,8 @@ module VagrantPlugins attr_accessor :privileged attr_accessor :binary attr_accessor :keep_color + attr_accessor :name + attr_accessor :powershell_args def initialize @args = UNSET_VALUE @@ -19,6 +21,8 @@ module VagrantPlugins @privileged = UNSET_VALUE @binary = UNSET_VALUE @keep_color = UNSET_VALUE + @name = UNSET_VALUE + @powershell_args = UNSET_VALUE end def finalize! @@ -29,6 +33,8 @@ module VagrantPlugins @privileged = true if @privileged == UNSET_VALUE @binary = false if @binary == UNSET_VALUE @keep_color = false if @keep_color == UNSET_VALUE + @name = nil if @name == UNSET_VALUE + @powershell_args = "-ExecutionPolicy Bypass" if @powershell_args == UNSET_VALUE if @args && args_valid? @args = @args.is_a?(Array) ? @args.map { |a| a.to_s } : @args.to_s diff --git a/plugins/provisioners/shell/provisioner.rb b/plugins/provisioners/shell/provisioner.rb index 674c9c5fb..4ee302f5d 100644 --- a/plugins/provisioners/shell/provisioner.rb +++ b/plugins/provisioners/shell/provisioner.rb @@ -2,10 +2,13 @@ require "pathname" require "tempfile" require "vagrant/util/downloader" +require "vagrant/util/retryable" module VagrantPlugins module Shell class Provisioner < Vagrant.plugin("2", :provisioner) + include Vagrant::Util::Retryable + def provision args = "" if config.args.is_a?(String) @@ -50,13 +53,22 @@ module VagrantPlugins # Upload the script to the machine @machine.communicate.tap do |comm| # Reset upload path permissions for the current ssh user - user = @machine.ssh_info[:username] + info = nil + retryable(on: Vagrant::Errors::SSHNotReady, tries: 3, sleep: 2) do + info = @machine.ssh_info + raise Vagrant::Errors::SSHNotReady if info.nil? + end + + user = info[:username] comm.sudo("chown -R #{user} #{config.upload_path}", error_check: false) comm.upload(path.to_s, config.upload_path) - if config.path + if config.name + @machine.ui.detail(I18n.t("vagrant.provisioners.shell.running", + script: "script: #{config.name}")) + elsif config.path @machine.ui.detail(I18n.t("vagrant.provisioners.shell.running", script: path.to_s)) else @@ -100,14 +112,25 @@ module VagrantPlugins exec_path.gsub!('/', '\\') exec_path = "c:#{exec_path}" if exec_path.start_with?("\\") - # For PowerShell scripts bypass the execution policy + # Copy powershell_args from configuration + shell_args = config.powershell_args + + # For PowerShell scripts bypass the execution policy unless already specified + shell_args += " -ExecutionPolicy Bypass" if config.powershell_args !~ /[-\/]ExecutionPolicy/i + + # CLIXML output is kinda useless, especially on non-windows hosts + shell_args += " -OutputFormat Text" if config.powershell_args !~ /[-\/]OutputFormat/i + command = "#{exec_path}#{args}" - command = "powershell -executionpolicy bypass -file #{command}" if + command = "powershell #{shell_args.to_s} -file #{command}" if File.extname(exec_path).downcase == '.ps1' - if config.path + if config.name @machine.ui.detail(I18n.t("vagrant.provisioners.shell.running", - script: exec_path)) + script: "script: #{config.name}")) + elsif config.path + @machine.ui.detail(I18n.t("vagrant.provisioners.shell.runningas", + local: config.path.to_s, remote: exec_path)) else @machine.ui.detail(I18n.t("vagrant.provisioners.shell.running", script: "inline PowerShell script")) diff --git a/plugins/pushes/atlas/config.rb b/plugins/pushes/atlas/config.rb new file mode 100644 index 000000000..0bedf5d80 --- /dev/null +++ b/plugins/pushes/atlas/config.rb @@ -0,0 +1,148 @@ +module VagrantPlugins + module AtlasPush + class Config < Vagrant.plugin("2", :config) + # The address of the Atlas server to upload to. By default this will + # be the public Atlas server. + # + # @return [String] + attr_accessor :address + + # The Atlas token to use. If the user has run `vagrant login`, this will + # use that token. If the environment variable `ATLAS_TOKEN` is set, the + # uploader will use this value. By default, this is nil. + # + # @return [String, nil] + attr_accessor :token + + # The name of the application to push to. This will be created (with + # user confirmation) if it doesn't already exist. + # + # @return [String] + attr_accessor :app + + # The base directory with file contents to upload. By default this + # is the same directory as the Vagrantfile, but you can specify this + # if you have a `src` folder or `bin` folder or some other folder + # you want to upload. + # + # @return [String] + attr_accessor :dir + + # Lists of files to include/exclude in what is uploaded. Exclude is + # always the last run filter, so if a file is matched in both include + # and exclude, it will be excluded. + # + # The value of the array elements should be a simple file glob relative + # to the directory being packaged. + # + # @return [Array] + attr_accessor :includes + attr_accessor :excludes + + # If set to true, Vagrant will automatically use VCS data to determine + # the files to upload. As a caveat: uncommitted changes will not be + # deployed. + # + # @return [Boolean] + attr_accessor :vcs + + # The path to the uploader binary to shell out to. This usually + # is only set for debugging/development. If not set, the uploader + # will be looked for within the Vagrant installer dir followed by + # the PATH. + # + # @return [String] + attr_accessor :uploader_path + + def initialize + @address = UNSET_VALUE + @token = UNSET_VALUE + @app = UNSET_VALUE + @dir = UNSET_VALUE + @vcs = UNSET_VALUE + @includes = [] + @excludes = [] + @uploader_path = UNSET_VALUE + end + + def merge(other) + super.tap do |result| + result.includes = self.includes.dup.concat(other.includes).uniq + result.excludes = self.excludes.dup.concat(other.excludes).uniq + end + end + + def finalize! + @address = nil if @address == UNSET_VALUE + @token = nil if @token == UNSET_VALUE + @token = ENV["ATLAS_TOKEN"] if !@token && ENV["ATLAS_TOKEN"] != "" + @app = nil if @app == UNSET_VALUE + @dir = "." if @dir == UNSET_VALUE + @uploader_path = nil if @uploader_path == UNSET_VALUE + @vcs = true if @vcs == UNSET_VALUE + end + + def validate(machine) + errors = _detected_errors + + if missing?(@token) + token = token_from_vagrant_login(machine.env) + if missing?(token) + errors << I18n.t("atlas_push.errors.missing_token") + else + @token = token + end + end + + if missing?(@app) + errors << I18n.t("atlas_push.errors.missing_attribute", + attribute: "app", + ) + end + + if missing?(@dir) + errors << I18n.t("atlas_push.errors.missing_attribute", + attribute: "dir", + ) + end + + { "Atlas push" => errors } + end + + # Add the filepath to the list of includes + # @param [String] filepath + def include(filepath) + @includes << filepath + end + alias_method :include=, :include + + # Add the filepath to the list of excludes + # @param [String] filepath + def exclude(filepath) + @excludes << filepath + end + alias_method :exclude=, :exclude + + private + + # Determine if the given string is "missing" (blank) + # @return [true, false] + def missing?(obj) + obj.to_s.strip.empty? + end + + # Attempt to load the token from disk using the vagrant-login plugin. If + # the constant is not defined, that means the user is operating in some + # bespoke and unsupported Ruby environment. + # + # @param [Vagrant::Environment] env + # + # @return [String, nil] + # the token, or nil if it does not exist + def token_from_vagrant_login(env) + client = VagrantPlugins::LoginCommand::Client.new(env) + client.token + end + end + end +end diff --git a/plugins/pushes/atlas/errors.rb b/plugins/pushes/atlas/errors.rb new file mode 100644 index 000000000..7ba1d712a --- /dev/null +++ b/plugins/pushes/atlas/errors.rb @@ -0,0 +1,13 @@ +module VagrantPlugins + module AtlasPush + module Errors + class Error < Vagrant::Errors::VagrantError + error_namespace("atlas_push.errors") + end + + class UploaderNotFound < Error + error_key(:uploader_not_found) + end + end + end +end diff --git a/plugins/pushes/atlas/locales/en.yml b/plugins/pushes/atlas/locales/en.yml new file mode 100644 index 000000000..3377f62dd --- /dev/null +++ b/plugins/pushes/atlas/locales/en.yml @@ -0,0 +1,22 @@ +en: + atlas_push: + errors: + missing_attribute: |- + Missing required attribute '%{attribute}'. The Vagrant Atlas Push plugin + requires you set this attribute. Please set this attribute in your + Vagrantfile, for example: + + config.push.define "atlas" do |push| + push.%{attribute} = "..." + end + missing_token: |- + Missing required configuration parameter 'token'. This is required for + Vagrant to securely communicate with your Atlas account. + + To generate an access token, run 'vagrant login'. + uploader_not_found: |- + Vagrant was unable to find the Atlas uploader CLI. If your Vagrantfile + specifies the path explicitly with "uploader_path", then make sure that + path is valid. Otherwise, make sure that you have a valid install of + Vagrant. If you installed Vagrant outside of the official installers, + the "atlas-upload" binary must exist on your PATH. diff --git a/plugins/pushes/atlas/plugin.rb b/plugins/pushes/atlas/plugin.rb new file mode 100644 index 000000000..2eee2517d --- /dev/null +++ b/plugins/pushes/atlas/plugin.rb @@ -0,0 +1,35 @@ +require "vagrant" + +module VagrantPlugins + module AtlasPush + autoload :Errors, File.expand_path("../errors", __FILE__) + + class Plugin < Vagrant.plugin("2") + name "atlas" + description <<-DESC + Deploy using HashiCorp's Atlas service. + DESC + + config(:atlas, :push) do + require_relative "config" + init! + Config + end + + push(:atlas) do + require_relative "push" + init! + Push + end + + protected + + def self.init! + return if defined?(@_init) + I18n.load_path << File.expand_path("../locales/en.yml", __FILE__) + I18n.reload! + @_init = true + end + end + end +end diff --git a/plugins/pushes/atlas/push.rb b/plugins/pushes/atlas/push.rb new file mode 100644 index 000000000..73ec117b9 --- /dev/null +++ b/plugins/pushes/atlas/push.rb @@ -0,0 +1,78 @@ +require "vagrant/util/safe_exec" +require "vagrant/util/subprocess" +require "vagrant/util/which" + +module VagrantPlugins + module AtlasPush + class Push < Vagrant.plugin("2", :push) + UPLOADER_BIN = "atlas-upload".freeze + + def push + uploader = self.uploader_path + + # If we didn't find the uploader binary it is a critical error + raise Errors::UploaderNotFound if !uploader + + # We found it. Build up the command and the args. + execute(uploader) + return 0 + end + + # Executes the uploader with the proper flags based on the configuration. + # This function shouldn't return since it will exec, but might return + # if we're on a system that doesn't support exec, so handle that properly. + def execute(uploader) + cmd = [] + cmd << "-debug" if !Vagrant.log_level.nil? + cmd << "-vcs" if config.vcs + cmd += config.includes.map { |v| ["-include", v] } + cmd += config.excludes.map { |v| ["-exclude", v] } + cmd += metadata.map { |k,v| ["-metadata", "#{k}=#{v}"] } + cmd += ["-address", config.address] if config.address + cmd += ["-token", config.token] if config.token + cmd << config.app + cmd << File.expand_path(config.dir, env.root_path) + Vagrant::Util::SafeExec.exec(uploader, *cmd.flatten) + end + + # This returns the path to the uploader binary, or nil if it can't + # be found. + # + # @return [String] + def uploader_path + # Determine the uploader path + if uploader = config.uploader_path + return uploader + end + + if Vagrant.in_installer? + path = File.join( + Vagrant.installer_embedded_dir, "bin", UPLOADER_BIN) + return path if File.file?(path) + end + + return Vagrant::Util::Which.which(UPLOADER_BIN) + end + + # The metadata command for this push. + # + # @return [Array] + def metadata + box = env.vagrantfile.config.vm.box + box_url = env.vagrantfile.config.vm.box_url + + result = {} + + if !box.nil? && !box.empty? + result["box"] = box + end + + if !box_url.nil? && !box_url.empty? + result["box_url"] = Array(box_url).first + end + + return result + end + end + end +end diff --git a/plugins/pushes/ftp/adapter.rb b/plugins/pushes/ftp/adapter.rb new file mode 100644 index 000000000..d0d076491 --- /dev/null +++ b/plugins/pushes/ftp/adapter.rb @@ -0,0 +1,127 @@ +require "pathname" + +module VagrantPlugins + module FTPPush + class Adapter + attr_reader :host + attr_reader :port + attr_reader :username + attr_reader :password + attr_reader :options + attr_reader :server + + def initialize(host, username, password, options = {}) + @host, @port = parse_host(host) + @username = username + @password = password + @options = options + @server = nil + end + + # Parse the host into it's url and port parts. + # @return [Array] + def parse_host(host) + if host.include?(":") + split = host.split(":", 2) + [split[0], split[1].to_i] + else + [host, default_port] + end + end + + def default_port + raise NotImplementedError + end + + def connect(&block) + raise NotImplementedError + end + + def upload(local, remote) + raise NotImplementedError + end + end + + # + # The FTP Adapter + # + class FTPAdapter < Adapter + def initialize(*) + require "net/ftp" + super + end + + def default_port + 21 + end + + def connect(&block) + @server = Net::FTP.new + @server.passive = options.fetch(:passive, true) + @server.connect(host, port) + @server.login(username, password) + + begin + yield self + ensure + @server.close + end + end + + def upload(local, remote) + parent = File.dirname(remote) + fullpath = Pathname.new(File.expand_path(parent, pwd)) + + # Create the parent directories if they does not exist (naive mkdir -p) + fullpath.descend do |path| + if !directory_exists?(path.to_s) + @server.mkdir(path.to_s) + end + end + + # Upload the file + @server.putbinaryfile(local, remote) + end + + def directory_exists?(path) + begin + @server.chdir(path) + return true + rescue Net::FTPPermError + return false + end + end + + private + + def pwd + @pwd ||= @server.pwd + end + end + + # + # The SFTP Adapter + # + class SFTPAdapter < Adapter + def initialize(*) + require "net/sftp" + super + end + + def default_port + 22 + end + + def connect(&block) + Net::SFTP.start(@host, @username, password: @password, port: @port) do |server| + @server = server + yield self + end + end + + def upload(local, remote) + @server.upload!(local, remote, mkdir: true) + end + end + end +end diff --git a/plugins/pushes/ftp/config.rb b/plugins/pushes/ftp/config.rb new file mode 100644 index 000000000..e493259db --- /dev/null +++ b/plugins/pushes/ftp/config.rb @@ -0,0 +1,130 @@ +module VagrantPlugins + module FTPPush + class Config < Vagrant.plugin("2", :config) + # The (S)FTP host to use. + # @return [String] + attr_accessor :host + + # The username to use for authentication with the (S)FTP server. + # @return [String] + attr_accessor :username + + # The password to use for authentication with the (S)FTP server. + # @return [String] + attr_accessor :password + + # Use passive FTP (default is true). + # @return [true, false] + attr_accessor :passive + + # Use secure (SFTP) (default is false). + # @return [true, false] + attr_accessor :secure + + # The root destination on the target system to sync the files (default is + # /). + # @return [String] + attr_accessor :destination + + # Lists of files to include/exclude in what is uploaded. Exclude is + # always the last run filter, so if a file is matched in both include + # and exclude, it will be excluded. + # + # The value of the array elements should be a simple file glob relative + # to the directory being packaged. + # @return [Array] + attr_accessor :includes + attr_accessor :excludes + + # The base directory with file contents to upload. By default this + # is the same directory as the Vagrantfile, but you can specify this + # if you have a `src` folder or `bin` folder or some other folder + # you want to upload. + # @return [String] + attr_accessor :dir + + def initialize + @host = UNSET_VALUE + @username = UNSET_VALUE + @password = UNSET_VALUE + @passive = UNSET_VALUE + @secure = UNSET_VALUE + @destination = UNSET_VALUE + + @includes = [] + @excludes = [] + + @dir = UNSET_VALUE + end + + def merge(other) + super.tap do |result| + result.includes = self.includes.dup.concat(other.includes).uniq + result.excludes = self.excludes.dup.concat(other.excludes).uniq + end + end + + def finalize! + @host = nil if @host == UNSET_VALUE + @username = nil if @username == UNSET_VALUE + @password = nil if @password == UNSET_VALUE + @passive = true if @passive == UNSET_VALUE + @secure = false if @secure == UNSET_VALUE + @destination = "/" if @destination == UNSET_VALUE + @dir = "." if @dir == UNSET_VALUE + end + + def validate(machine) + errors = _detected_errors + + if missing?(@host) + errors << I18n.t("ftp_push.errors.missing_attribute", + attribute: "host", + ) + end + + if missing?(@username) + errors << I18n.t("ftp_push.errors.missing_attribute", + attribute: "username", + ) + end + + if missing?(@destination) + errors << I18n.t("ftp_push.errors.missing_attribute", + attribute: "destination", + ) + end + + if missing?(@dir) + errors << I18n.t("ftp_push.errors.missing_attribute", + attribute: "dir", + ) + end + + { "FTP push" => errors } + end + + # Add the filepath to the list of includes + # @param [String] filepath + def include(filepath) + @includes << filepath + end + alias_method :include=, :include + + # Add the filepath to the list of excludes + # @param [String] filepath + def exclude(filepath) + @excludes << filepath + end + alias_method :exclude=, :exclude + + private + + # Determine if the given string is "missing" (blank) + # @return [true, false] + def missing?(obj) + obj.to_s.strip.empty? + end + end + end +end diff --git a/plugins/pushes/ftp/locales/en.yml b/plugins/pushes/ftp/locales/en.yml new file mode 100644 index 000000000..0bbbe51f1 --- /dev/null +++ b/plugins/pushes/ftp/locales/en.yml @@ -0,0 +1,11 @@ +en: + ftp_push: + errors: + missing_attribute: |- + Missing required attribute '%{attribute}'. The Vagrant FTP Push plugin + requires you set this attribute. Please set this attribute in your + Vagrantfile, for example: + + config.push.define "ftp" do |push| + push.%{attribute} = "..." + end diff --git a/plugins/pushes/ftp/plugin.rb b/plugins/pushes/ftp/plugin.rb new file mode 100644 index 000000000..ef333807b --- /dev/null +++ b/plugins/pushes/ftp/plugin.rb @@ -0,0 +1,33 @@ +require "vagrant" + +module VagrantPlugins + module FTPPush + class Plugin < Vagrant.plugin("2") + name "ftp" + description <<-DESC + Deploy to a remote FTP or SFTP server. + DESC + + config(:ftp, :push) do + require File.expand_path("../config", __FILE__) + init! + Config + end + + push(:ftp) do + require File.expand_path("../push", __FILE__) + init! + Push + end + + protected + + def self.init! + return if defined?(@_init) + I18n.load_path << File.expand_path("../locales/en.yml", __FILE__) + I18n.reload! + @_init = true + end + end + end +end diff --git a/plugins/pushes/ftp/push.rb b/plugins/pushes/ftp/push.rb new file mode 100644 index 000000000..7a69c4f02 --- /dev/null +++ b/plugins/pushes/ftp/push.rb @@ -0,0 +1,120 @@ +require "net/ftp" +require "pathname" + +require_relative "adapter" + +module VagrantPlugins + module FTPPush + class Push < Vagrant.plugin("2", :push) + IGNORED_FILES = %w(. ..).freeze + DEFAULT_EXCLUDES = %w(.git .hg .svn .vagrant).freeze + + def initialize(*) + super + @logger = Log4r::Logger.new("vagrant::pushes::ftp") + end + + def push + # Grab files early so if there's an exception or issue, we don't have to + # wait and close the (S)FTP connection as well + files = Hash[*all_files.flat_map do |file| + relative_path = relative_path_for(file, config.dir) + destination = File.expand_path(File.join(config.destination, relative_path)) + file = File.expand_path(file, env.root_path) + [file, destination] + end] + + ftp = "#{config.username}@#{config.host}:#{config.destination}" + env.ui.info "Uploading #{env.root_path} to #{ftp}" + + connect do |ftp| + files.each do |local, remote| + @logger.info "Uploading #{local} => #{remote}" + ftp.upload(local, remote) + end + end + end + + # Helper method for creating the FTP or SFTP connection. + # @yield [Adapter] + def connect(&block) + klass = config.secure ? SFTPAdapter : FTPAdapter + ftp = klass.new(config.host, config.username, config.password, + passive: config.passive) + ftp.connect(&block) + end + + # The list of all files that should be pushed by this push. This method + # only returns **files**, not folders or symlinks! + # @return [Array] + def all_files + files = glob("#{config.dir}/**/*") + includes_files + filter_excludes!(files, config.excludes) + files.reject! { |f| !File.file?(f) } + files + end + + # The list of files to include in addition to those specified in `dir`. + # @return [Array] + def includes_files + includes = config.includes.flat_map do |i| + path = absolute_path_for(i, config.dir) + [path, "#{path}/**/*"] + end + + glob("{#{includes.join(",")}}") + end + + # Filter the excludes out of the given list. This method modifies the + # given list in memory! + # + # @param [Array] list + # the filepaths + # @param [Array] excludes + # the exclude patterns or files + def filter_excludes!(list, excludes) + excludes = Array(excludes) + excludes = excludes + DEFAULT_EXCLUDES + excludes = excludes.flat_map { |e| [e, "#{e}/*"] } + + list.reject! do |file| + basename = relative_path_for(file, config.dir) + + # Handle the special case where the file is outside of the working + # directory... + if basename.start_with?("../") + basename = file + end + + excludes.any? { |e| File.fnmatch?(e, basename, File::FNM_DOTMATCH) } + end + end + + # Get the list of files that match the given pattern. + # @return [Array] + def glob(pattern) + Dir.glob(pattern, File::FNM_DOTMATCH).sort.reject do |file| + IGNORED_FILES.include?(File.basename(file)) + end + end + + # The absolute path to the given `path` and `parent`, unless the given + # path is absolute. + # @return [String] + def absolute_path_for(path, parent) + path = Pathname.new(path) + return path if path.absolute? + File.expand_path(path, parent) + end + + # The relative path from the given `parent`. If files exist on another + # device, this will probably blow up. + # @return [String] + def relative_path_for(path, parent) + Pathname.new(path).relative_path_from(Pathname.new(parent)).to_s + rescue ArgumentError + return path + end + end + end +end diff --git a/plugins/pushes/heroku/config.rb b/plugins/pushes/heroku/config.rb new file mode 100644 index 000000000..927c1a7b9 --- /dev/null +++ b/plugins/pushes/heroku/config.rb @@ -0,0 +1,74 @@ +module VagrantPlugins + module HerokuPush + class Config < Vagrant.plugin("2", :config) + # The name of the Heroku application to push to. + # @return [String] + attr_accessor :app + + # The base directory with file contents to upload. By default this + # is the same directory as the Vagrantfile, but you can specify this + # if you have a `src` folder or `bin` folder or some other folder + # you want to upload. This directory must be a git repository. + # @return [String] + attr_accessor :dir + + # The path to the git binary to shell out to. This usually is only set for + # debugging/development. If not set, the git bin will be searched for + # in the PATH. + # @return [String] + attr_accessor :git_bin + + # The Git remote to push to (default: "heroku"). + # @return [String] + attr_accessor :remote + + def initialize + @app = UNSET_VALUE + @dir = UNSET_VALUE + + @git_bin = UNSET_VALUE + @remote = UNSET_VALUE + end + + def finalize! + @app = nil if @app == UNSET_VALUE + @dir = "." if @dir == UNSET_VALUE + + @git_bin = "git" if @git_bin == UNSET_VALUE + @remote = "heroku" if @remote == UNSET_VALUE + end + + def validate(machine) + errors = _detected_errors + + if missing?(@dir) + errors << I18n.t("heroku_push.errors.missing_attribute", + attribute: "dir", + ) + end + + if missing?(@git_bin) + errors << I18n.t("heroku_push.errors.missing_attribute", + attribute: "git_bin", + ) + end + + if missing?(@remote) + errors << I18n.t("heroku_push.errors.missing_attribute", + attribute: "remote", + ) + end + + { "Heroku push" => errors } + end + + private + + # Determine if the given string is "missing" (blank) + # @return [true, false] + def missing?(obj) + obj.to_s.strip.empty? + end + end + end +end diff --git a/plugins/pushes/heroku/errors.rb b/plugins/pushes/heroku/errors.rb new file mode 100644 index 000000000..c92a7b76c --- /dev/null +++ b/plugins/pushes/heroku/errors.rb @@ -0,0 +1,21 @@ +module VagrantPlugins + module HerokuPush + module Errors + class Error < Vagrant::Errors::VagrantError + error_namespace("heroku_push.errors") + end + + class CommandFailed < Error + error_key(:command_failed) + end + + class GitNotFound < Error + error_key(:git_not_found) + end + + class NotAGitRepo < Error + error_key(:not_a_git_repo) + end + end + end +end diff --git a/plugins/pushes/heroku/locales/en.yml b/plugins/pushes/heroku/locales/en.yml new file mode 100644 index 000000000..e6f6bf441 --- /dev/null +++ b/plugins/pushes/heroku/locales/en.yml @@ -0,0 +1,30 @@ +en: + heroku_push: + errors: + command_failed: |- + The following command exited with a non-zero exit status: + + %{cmd} + + stdout: %{stdout} + stderr: %{stderr} + git_not_found: |- + The Git binary '%{bin}' could not be found. Please ensure you + have downloaded and installed the latest version of Git: + + http://git-scm.com/downloads + missing_attribute: |- + Missing required attribute '%{attribute}'. The Vagrant Heroku Push + plugin requires you set this attribute. Please set this attribute in + your Vagrantfile, for example: + + config.push.define "heroku" do |push| + push.%{attribute} = "..." + end + not_a_git_repo: |- + The following path is not a valid Git repository: + + %{path} + + Please ensure you are working in the correct directory. In order to use + the Vagrant Heroku Push plugin, you must have a git repository. diff --git a/plugins/pushes/heroku/plugin.rb b/plugins/pushes/heroku/plugin.rb new file mode 100644 index 000000000..40865ee5d --- /dev/null +++ b/plugins/pushes/heroku/plugin.rb @@ -0,0 +1,33 @@ +require "vagrant" + +module VagrantPlugins + module HerokuPush + class Plugin < Vagrant.plugin("2") + name "heroku" + description <<-DESC + Deploy to a Heroku + DESC + + config(:heroku, :push) do + require File.expand_path("../config", __FILE__) + init! + Config + end + + push(:heroku) do + require File.expand_path("../push", __FILE__) + init! + Push + end + + protected + + def self.init! + return if defined?(@_init) + I18n.load_path << File.expand_path("../locales/en.yml", __FILE__) + I18n.reload! + @_init = true + end + end + end +end diff --git a/plugins/pushes/heroku/push.rb b/plugins/pushes/heroku/push.rb new file mode 100644 index 000000000..b894eba81 --- /dev/null +++ b/plugins/pushes/heroku/push.rb @@ -0,0 +1,136 @@ +require "vagrant/util/subprocess" +require "vagrant/util/which" + +require_relative "errors" + +module VagrantPlugins + module HerokuPush + class Push < Vagrant.plugin("2", :push) + def push + # Expand any paths relative to the root + dir = File.expand_path(config.dir, env.root_path) + + # Verify git is installed + verify_git_bin!(config.git_bin) + + # Verify we are operating in a git repo + verify_git_repo!(dir) + + # Get the current branch + branch = git_branch(dir) + + # Get the name of the app + app = config.app || interpret_app(dir) + + # Check if we need to add the git remote + if !has_git_remote?(config.remote, dir) + add_heroku_git_remote(config.remote, app, dir) + end + + # Push to Heroku + git_push_heroku(config.remote, branch, dir) + end + + # Verify that git is installed. + # @raise [Errors::GitNotFound] + def verify_git_bin!(path) + if Vagrant::Util::Which.which(path).nil? + raise Errors::GitNotFound, bin: path + end + end + + # Verify that the given path is a git directory. + # @raise [Errors::NotAGitRepo] + # @param [String] + def verify_git_repo!(path) + if !File.directory?(git_dir(path)) + raise Errors::NotAGitRepo, path: path + end + end + + # Interpret the name of the Heroku application from the given path. + # @param [String] path + # @return [String] + def interpret_app(path) + File.basename(path) + end + + # The git directory for the given path. + # @param [String] path + # @return [String] + def git_dir(path) + "#{path}/.git" + end + + # The name of the current git branch. + # @param [String] path + # @return [String] + def git_branch(path) + result = execute!("git", + "--git-dir", git_dir(path), + "--work-tree", path, + "branch", + ) + + # Returns something like "* master" + result.stdout.sub("*", "").strip + end + + # Push to the Heroku remote. + # @param [String] remote + # @param [String] branch + def git_push_heroku(remote, branch, path) + execute!("git", + "--git-dir", git_dir(path), + "--work-tree", path, + "push", remote, "#{branch}:master", + ) + end + + # Check if the git remote has the given remote. + # @param [String] remote + # @return [true, false] + def has_git_remote?(remote, path) + result = execute!("git", + "--git-dir", git_dir(path), + "--work-tree", path, + "remote", + ) + remotes = result.stdout.split(/\r?\n/).map(&:strip) + remotes.include?(remote.to_s) + end + + # Add the Heroku to the current repository. + # @param [String] remote + # @param [String] app + def add_heroku_git_remote(remote, app, path) + execute!("git", + "--git-dir", git_dir(path), + "--work-tree", path, + "remote", "add", remote, heroku_git_url(app), + ) + end + + # The URL for this project on Heroku. + # @return [String] + def heroku_git_url(app) + "git@heroku.com:#{app}.git" + end + + # Execute the command, raising an exception if it fails. + # @return [Vagrant::Util::Subprocess::Result] + def execute!(*cmd) + result = Vagrant::Util::Subprocess.execute(*cmd) + + if result.exit_code != 0 + raise Errors::CommandFailed, + cmd: cmd.join(" "), + stdout: result.stdout, + stderr: result.stderr + end + + result + end + end + end +end diff --git a/plugins/pushes/local-exec/config.rb b/plugins/pushes/local-exec/config.rb new file mode 100644 index 000000000..747ff8925 --- /dev/null +++ b/plugins/pushes/local-exec/config.rb @@ -0,0 +1,48 @@ +module VagrantPlugins + module LocalExecPush + class Config < Vagrant.plugin("2", :config) + # The path (relative to the machine root) to a local script that will be + # executed. + # @return [String] + attr_accessor :script + + # The command (as a string) to execute. + # @return [String] + attr_accessor :inline + + def initialize + @script = UNSET_VALUE + @inline = UNSET_VALUE + end + + def finalize! + @script = nil if @script == UNSET_VALUE + @inline = nil if @inline == UNSET_VALUE + end + + def validate(machine) + errors = _detected_errors + + if missing?(@script) && missing?(@inline) + errors << I18n.t("local_exec_push.errors.missing_attribute", + attribute: "script", + ) + end + + if !missing?(@script) && !missing?(@inline) + errors << I18n.t("local_exec_push.errors.cannot_specify_script_and_inline") + end + + { "Local Exec push" => errors } + end + + private + + # Determine if the given string is "missing" (blank) + # @return [true, false] + def missing?(obj) + obj.to_s.strip.empty? + end + end + end +end diff --git a/plugins/pushes/local-exec/errors.rb b/plugins/pushes/local-exec/errors.rb new file mode 100644 index 000000000..5e5b71cca --- /dev/null +++ b/plugins/pushes/local-exec/errors.rb @@ -0,0 +1,13 @@ +module VagrantPlugins + module LocalExecPush + module Errors + class Error < Vagrant::Errors::VagrantError + error_namespace("local_exec_push.errors") + end + + class CommandFailed < Error + error_key(:command_failed) + end + end + end +end diff --git a/plugins/pushes/local-exec/locales/en.yml b/plugins/pushes/local-exec/locales/en.yml new file mode 100644 index 000000000..08808a879 --- /dev/null +++ b/plugins/pushes/local-exec/locales/en.yml @@ -0,0 +1,22 @@ +en: + local_exec_push: + errors: + cannot_specify_script_and_inline: |- + You have specified both the 'script' and 'inline' attributes for the + Vagrant Local Exec Push plugin. You may only specify one of these + attributes. + command_failed: |- + The following command exited with a non-zero exit status: + + %{cmd} + + stdout: %{stdout} + stderr: %{stderr} + missing_attribute: |- + Missing required attribute '%{attribute}'. The Vagrant Local Exec Push + plugin requires you set this attribute. Please set this attribute in + your Vagrantfile, for example: + + config.push.define "local-exec" do |push| + push.%{attribute} = "..." + end diff --git a/plugins/pushes/local-exec/plugin.rb b/plugins/pushes/local-exec/plugin.rb new file mode 100644 index 000000000..e58fe4bed --- /dev/null +++ b/plugins/pushes/local-exec/plugin.rb @@ -0,0 +1,33 @@ +require "vagrant" + +module VagrantPlugins + module LocalExecPush + class Plugin < Vagrant.plugin("2") + name "local-exec" + description <<-DESC + Run a local command or script to push + DESC + + config(:"local-exec", :push) do + require File.expand_path("../config", __FILE__) + init! + Config + end + + push(:"local-exec") do + require File.expand_path("../push", __FILE__) + init! + Push + end + + protected + + def self.init! + return if defined?(@_init) + I18n.load_path << File.expand_path("../locales/en.yml", __FILE__) + I18n.reload! + @_init = true + end + end + end +end diff --git a/plugins/pushes/local-exec/push.rb b/plugins/pushes/local-exec/push.rb new file mode 100644 index 000000000..718b1ab04 --- /dev/null +++ b/plugins/pushes/local-exec/push.rb @@ -0,0 +1,46 @@ +require "fileutils" +require "tempfile" +require "vagrant/util/safe_exec" + +require_relative "errors" + +module VagrantPlugins + module LocalExecPush + class Push < Vagrant.plugin("2", :push) + def push + if config.inline + execute_inline!(config.inline) + else + execute_script!(config.script) + end + end + + # Execute the inline script by writing it to a tempfile and executing. + def execute_inline!(inline) + script = Tempfile.new(["vagrant-local-exec-script", ".sh"]) + script.write(inline) + script.rewind + script.close + + execute_script!(script.path) + ensure + if script + script.close + script.unlink + end + end + + # Execute the script, expanding the path relative to the current env root. + def execute_script!(path) + path = File.expand_path(path, env.root_path) + FileUtils.chmod("+x", path) + execute!(path) + end + + # Execute the script, raising an exception if it fails. + def execute!(*cmd) + Vagrant::Util::SafeExec.exec(cmd[0], *cmd[1..-1]) + end + end + end +end diff --git a/plugins/pushes/noop/config.rb b/plugins/pushes/noop/config.rb new file mode 100644 index 000000000..9b23fb450 --- /dev/null +++ b/plugins/pushes/noop/config.rb @@ -0,0 +1,16 @@ +module VagrantPlugins + module NoopDeploy + class Config < Vagrant.plugin("2", :config) + def initialize + end + + def finalize! + end + + def validate(machine) + errors = _detected_errors + { "Noop push" => errors } + end + end + end +end diff --git a/plugins/pushes/noop/plugin.rb b/plugins/pushes/noop/plugin.rb new file mode 100644 index 000000000..d3dc6994d --- /dev/null +++ b/plugins/pushes/noop/plugin.rb @@ -0,0 +1,22 @@ +require "vagrant" + +module VagrantPlugins + module NoopDeploy + class Plugin < Vagrant.plugin("2") + name "noop" + description <<-DESC + Literally do nothing + DESC + + config(:noop, :push) do + require File.expand_path("../config", __FILE__) + Config + end + + push(:noop) do + require File.expand_path("../push", __FILE__) + Push + end + end + end +end diff --git a/plugins/pushes/noop/push.rb b/plugins/pushes/noop/push.rb new file mode 100644 index 000000000..537ea1c7d --- /dev/null +++ b/plugins/pushes/noop/push.rb @@ -0,0 +1,9 @@ +module VagrantPlugins + module NoopDeploy + class Push < Vagrant.plugin("2", :push) + def push + puts "pushed" + end + end + end +end diff --git a/plugins/synced_folders/nfs/config.rb b/plugins/synced_folders/nfs/config.rb index 88e4fa2e3..4d8cfdbc3 100644 --- a/plugins/synced_folders/nfs/config.rb +++ b/plugins/synced_folders/nfs/config.rb @@ -6,6 +6,7 @@ module VagrantPlugins attr_accessor :functional attr_accessor :map_uid attr_accessor :map_gid + attr_accessor :verify_installed def initialize super @@ -13,12 +14,14 @@ module VagrantPlugins @functional = UNSET_VALUE @map_uid = UNSET_VALUE @map_gid = UNSET_VALUE + @verify_installed = UNSET_VALUE end def finalize! @functional = true if @functional == UNSET_VALUE @map_uid = :auto if @map_uid == UNSET_VALUE @map_gid = :auto if @map_gid == UNSET_VALUE + @verify_installed = true if @verify_installed == UNSET_VALUE end def to_s diff --git a/plugins/synced_folders/nfs/synced_folder.rb b/plugins/synced_folders/nfs/synced_folder.rb index f84c43b33..8fe0f69c3 100644 --- a/plugins/synced_folders/nfs/synced_folder.rb +++ b/plugins/synced_folders/nfs/synced_folder.rb @@ -43,13 +43,15 @@ module VagrantPlugins raise Vagrant::Errors::NFSNoHostIP if !nfsopts[:nfs_host_ip] raise Vagrant::Errors::NFSNoGuestIP if !nfsopts[:nfs_machine_ip] - if machine.guest.capability?(:nfs_client_installed) - installed = machine.guest.capability(:nfs_client_installed) - if !installed - can_install = machine.guest.capability?(:nfs_client_install) - raise Vagrant::Errors::NFSClientNotInstalledInGuest if !can_install - machine.ui.info I18n.t("vagrant.actions.vm.nfs.installing") - machine.guest.capability(:nfs_client_install) + if machine.config.nfs.verify_installed + if machine.guest.capability?(:nfs_client_installed) + installed = machine.guest.capability(:nfs_client_installed) + if !installed + can_install = machine.guest.capability?(:nfs_client_install) + raise Vagrant::Errors::NFSClientNotInstalledInGuest if !can_install + machine.ui.info I18n.t("vagrant.actions.vm.nfs.installing") + machine.guest.capability(:nfs_client_install) + end end end @@ -64,25 +66,28 @@ module VagrantPlugins export_folders = folders.dup export_folders.keys.each do |id| opts = export_folders[id] - if opts.has_key?(:nfs_export) && !opts[:nfs_export] + if opts.key?(:nfs_export) && !opts[:nfs_export] export_folders.delete(id) end end - # Export the folders. We do this with a class-wide lock because - # NFS exporting often requires sudo privilege and we don't want - # overlapping input requests. [GH-2680] - @@lock.synchronize do - begin - machine.env.lock("nfs-export") do - machine.ui.info I18n.t("vagrant.actions.vm.nfs.exporting") - machine.env.host.capability( - :nfs_export, - machine.ui, machine.id, machine_ip, export_folders) + # Update the exports when there are actually exports [GH-4148] + if !export_folders.empty? + # Export the folders. We do this with a class-wide lock because + # NFS exporting often requires sudo privilege and we don't want + # overlapping input requests. [GH-2680] + @@lock.synchronize do + begin + machine.env.lock("nfs-export") do + machine.ui.info I18n.t("vagrant.actions.vm.nfs.exporting") + machine.env.host.capability( + :nfs_export, + machine.ui, machine.id, machine_ip, export_folders) + end + rescue Vagrant::Errors::EnvironmentLockedError + sleep 1 + retry end - rescue Vagrant::Errors::EnvironmentLockedError - sleep 1 - retry end end @@ -114,7 +119,7 @@ module VagrantPlugins def prepare_folder(machine, opts) opts[:map_uid] = prepare_permission(machine, :uid, opts) opts[:map_gid] = prepare_permission(machine, :gid, opts) - opts[:nfs_udp] = true if !opts.has_key?(:nfs_udp) + opts[:nfs_udp] = true if !opts.key?(:nfs_udp) opts[:nfs_version] ||= 3 # We use a CRC32 to generate a 32-bit checksum so that the @@ -125,11 +130,11 @@ module VagrantPlugins # Prepares the UID/GID settings for a single folder. def prepare_permission(machine, perm, opts) key = "map_#{perm}".to_sym - return nil if opts.has_key?(key) && opts[key].nil? + return nil if opts.key?(key) && opts[key].nil? # The options on the hash get priority, then the default # values - value = opts.has_key?(key) ? opts[key] : machine.config.nfs.send(key) + value = opts.key?(key) ? opts[key] : machine.config.nfs.send(key) return value if value != :auto # Get UID/GID from folder if we've made it this far diff --git a/plugins/synced_folders/rsync/command/rsync.rb b/plugins/synced_folders/rsync/command/rsync.rb index 6703ffaaa..0231db166 100644 --- a/plugins/synced_folders/rsync/command/rsync.rb +++ b/plugins/synced_folders/rsync/command/rsync.rb @@ -18,6 +18,11 @@ module VagrantPlugins opts = OptionParser.new do |o| o.banner = "Usage: vagrant rsync [vm-name]" o.separator "" + o.separator "This command forces any synced folders with type 'rsync' to sync." + o.separator "RSync is not an automatic sync so a manual command is used." + o.separator "" + o.separator "Options:" + o.separator "" end # Parse the options and return if we don't have any target. @@ -27,6 +32,18 @@ module VagrantPlugins # Go through each machine and perform the rsync error = false with_target_vms(argv) do |machine| + if machine.provider.capability?(:proxy_machine) + proxy = machine.provider.capability(:proxy_machine) + if proxy + machine.ui.warn(I18n.t( + "vagrant.rsync_proxy_machine", + name: machine.name.to_s, + provider: machine.provider_name.to_s)) + + machine = proxy + end + end + if !machine.communicate.ready? machine.ui.error(I18n.t("vagrant.rsync_communicator_not_ready")) error = true @@ -34,7 +51,7 @@ module VagrantPlugins end # Determine the rsync synced folders for this machine - folders = synced_folders(machine)[:rsync] + folders = synced_folders(machine, cached: true)[:rsync] next if !folders || folders.empty? # Get the SSH info for this machine so we can access it diff --git a/plugins/synced_folders/rsync/command/rsync_auto.rb b/plugins/synced_folders/rsync/command/rsync_auto.rb index 0ae393113..097e68cc3 100644 --- a/plugins/synced_folders/rsync/command/rsync_auto.rb +++ b/plugins/synced_folders/rsync/command/rsync_auto.rb @@ -48,6 +48,18 @@ module VagrantPlugins paths = {} ignores = [] with_target_vms(argv) do |machine| + if machine.provider.capability?(:proxy_machine) + proxy = machine.provider.capability(:proxy_machine) + if proxy + machine.ui.warn(I18n.t( + "vagrant.rsync_proxy_machine", + name: machine.name.to_s, + provider: machine.provider_name.to_s)) + + machine = proxy + end + end + cached = synced_folders(machine, cached: true) fresh = synced_folders(machine) diff = synced_folders_diff(cached, fresh) @@ -71,7 +83,7 @@ module VagrantPlugins folders.each do |id, folder_opts| # If we marked this folder to not auto sync, then # don't do it. - next if folder_opts.has_key?(:auto) && !folder_opts[:auto] + next if folder_opts.key?(:auto) && !folder_opts[:auto] hostpath = folder_opts[:hostpath] hostpath = File.expand_path(hostpath, machine.env.root_path) @@ -129,7 +141,7 @@ module VagrantPlugins Vagrant::Util::Busy.busy(callback) do listener.start queue.pop - listener.stop if listener.listen? + listener.stop if listener.state != :stopped end 0 @@ -179,6 +191,10 @@ module VagrantPlugins # halt is happening. Just notify the user but don't fail out. opts[:machine].ui.error(I18n.t( "vagrant.rsync_communicator_not_ready_callback")) + rescue Vagrant::Errors::RSyncError => e + # Error executing rsync, so show an error + opts[:machine].ui.error(I18n.t( + "vagrant.rsync_auto_rsync_error", message: e.to_s)) end end end diff --git a/plugins/synced_folders/rsync/helper.rb b/plugins/synced_folders/rsync/helper.rb index bc7f6d116..594410436 100644 --- a/plugins/synced_folders/rsync/helper.rb +++ b/plugins/synced_folders/rsync/helper.rb @@ -65,6 +65,7 @@ module VagrantPlugins "ssh -p #{ssh_info[:port]} " + proxy_command + "-o StrictHostKeyChecking=no " + + "-o IdentitiesOnly=true " + "-o UserKnownHostsFile=/dev/null", ssh_info[:private_key_path].map { |p| "-i '#{p}'" }, ].flatten.join(" ") @@ -97,8 +98,12 @@ module VagrantPlugins args << "--no-group" unless args.include?("--group") || args.include?("-g") # Tell local rsync how to invoke remote rsync with sudo - if machine.guest.capability?(:rsync_command) - args << "--rsync-path"<< machine.guest.capability(:rsync_command) + rsync_path = opts[:rsync_path] + if !rsync_path && machine.guest.capability?(:rsync_command) + rsync_path = machine.guest.capability(:rsync_command) + end + if rsync_path + args << "--rsync-path"<< rsync_path end # Build up the actual command to execute @@ -121,13 +126,25 @@ module VagrantPlugins machine.ui.info(I18n.t( "vagrant.rsync_folder_excludes", excludes: excludes.inspect)) end + if opts.include?(:verbose) + machine.ui.info(I18n.t("vagrant.rsync_showing_output")); + end # If we have tasks to do before rsyncing, do those. if machine.guest.capability?(:rsync_pre) machine.guest.capability(:rsync_pre, opts) end - r = Vagrant::Util::Subprocess.execute(*(command + [command_opts])) + if opts.include?(:verbose) + command_opts[:notify] = [:stdout, :stderr] + r = Vagrant::Util::Subprocess.execute(*(command + [command_opts])) { + |io_name,data| data.each_line { |line| + machine.ui.info("rsync[#{io_name}] -> #{line}") } + } + else + r = Vagrant::Util::Subprocess.execute(*(command + [command_opts])) + end + if r.exit_code != 0 raise Vagrant::Errors::RSyncError, command: command.join(" "), diff --git a/plugins/synced_folders/smb/scripts/host_info.ps1 b/plugins/synced_folders/smb/scripts/host_info.ps1 index 7d283a7f4..0e089932e 100644 --- a/plugins/synced_folders/smb/scripts/host_info.ps1 +++ b/plugins/synced_folders/smb/scripts/host_info.ps1 @@ -1,8 +1,11 @@ $ErrorAction = "Stop" -$net = Get-WmiObject -class win32_NetworkAdapterConfiguration -Filter 'ipenabled = "true"' +$net = Get-NetIPAddress | Where-Object { + ($_.IPAddress -ne "127.0.0.1") -and ($_.IPAddress -ne "::1") +} | Sort-Object $_.AddressFamily + $result = @{ - ip_addresses = $net.ipaddress + ip_addresses = $net.IPAddress } Write-Output $(ConvertTo-Json $result) diff --git a/plugins/synced_folders/smb/synced_folder.rb b/plugins/synced_folders/smb/synced_folder.rb index 2080efefc..e3f5a1af2 100644 --- a/plugins/synced_folders/smb/synced_folder.rb +++ b/plugins/synced_folders/smb/synced_folder.rb @@ -45,15 +45,17 @@ module VagrantPlugins script_path = File.expand_path("../scripts/set_share.ps1", __FILE__) # If we need auth information, then ask the user. - need_auth = false + have_auth = false folders.each do |id, data| - if !data[:smb_username] || !data[:smb_password] - need_auth = true + if data[:smb_username] && data[:smb_password] + @creds[:username] = data[:smb_username] + @creds[:password] = data[:smb_password] + have_auth = true break end end - if need_auth + if !have_auth machine.ui.detail(I18n.t("vagrant_sf_smb.warning_password") + "\n ") @creds[:username] = machine.ui.ask("Username: ") @creds[:password] = machine.ui.ask("Password (will be hidden): ", echo: false) @@ -150,29 +152,6 @@ module VagrantPlugins JSON.parse(r.stdout)["ip_addresses"] end - -=begin - def mount_shared_folders_to_windows - result = @env[:machine].provider.driver.execute('host_info.ps1', {}) - @smb_shared_folders.each do |id, data| - begin - options = { share_name: data[:share_name], - guest_path: data[:guestpath].gsub("/", "\\"), - guest_ip: ssh_info[:host], - username: ssh_info[:username], - host_ip: result["host_ip"], - password: @env[:machine].provider_config.guest.password, - host_share_username: @env[:machine].provider_config.host_share.username, - host_share_password: @env[:machine].provider_config.host_share.password} - @env[:ui].info("Linking #{data[:share_name]} to Guest at #{data[:guestpath]} ...") - @env[:machine].provider.driver.execute('mount_share.ps1', options) - rescue Error::SubprocessError => e - @env[:ui].info "Failed to link #{data[:share_name]} to Guest" - @env[:ui].info e.message - end - end - end -=end end end end diff --git a/scripts/website_push_docs.sh b/scripts/website_push_docs.sh index ec2d8a09f..9bcb167bd 100755 --- a/scripts/website_push_docs.sh +++ b/scripts/website_push_docs.sh @@ -8,5 +8,10 @@ DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )" # Change into that directory cd $DIR +# Add the git remote if it doesn't exist +git remote | grep heroku-docs || { + git remote add heroku-docs git@heroku.com:vagrantup-docs-2.git +} + # Push the subtree (force) git push heroku-docs `git subtree split --prefix website/docs master`:master --force diff --git a/scripts/website_push_www.sh b/scripts/website_push_www.sh index 9e8bff265..cc1e639b7 100755 --- a/scripts/website_push_www.sh +++ b/scripts/website_push_www.sh @@ -8,5 +8,10 @@ DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )" # Change into that directory cd $DIR +# Add the git remote if it doesn't exist +git remote | grep heroku-www || { + git remote add heroku-www git@heroku.com:vagrantup-www-2.git +} + # Push the subtree (force) git push heroku-www `git subtree split --prefix website/www master`:master --force diff --git a/tasks/test.rake b/tasks/test.rake index 18e4395b1..a6da5e8c9 100644 --- a/tasks/test.rake +++ b/tasks/test.rake @@ -4,11 +4,6 @@ require 'rspec/core/rake_task' namespace :test do RSpec::Core::RakeTask.new(:unit) do |t| t.pattern = "test/unit/**/*_test.rb" - end - - Rake::TestTask.new do |t| - t.name = "unit_old" - t.libs << "test/unit_legacy" - t.pattern = "test/unit_legacy/**/*_test.rb" + t.rspec_opts = "--color" end end diff --git a/templates/commands/init/Vagrantfile.erb b/templates/commands/init/Vagrantfile.erb index 7c8d05709..4cae3c990 100644 --- a/templates/commands/init/Vagrantfile.erb +++ b/templates/commands/init/Vagrantfile.erb @@ -1,15 +1,17 @@ # -*- mode: ruby -*- # vi: set ft=ruby : -# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! -VAGRANTFILE_API_VERSION = "2" +# All Vagrant configuration is done below. The "2" in Vagrant.configure +# configures the configuration version (we support older styles for +# backwards compatibility). Please don't change it unless you know what +# you're doing. +Vagrant.configure(2) do |config| + # The most common configuration options are documented and commented below. + # For a complete reference, please see the online documentation at + # https://docs.vagrantup.com. -Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| - # All Vagrant configuration is done here. The most common configuration - # options are documented and commented below. For a complete reference, - # please see the online documentation at vagrantup.com. - - # Every Vagrant virtual environment requires a box to build off of. + # Every Vagrant development environment requires a box. You can search for + # boxes at https://atlas.hashicorp.com/search. config.vm.box = "<%= box_name %>" <% if box_url -%> @@ -37,10 +39,6 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| # your network. # config.vm.network "public_network" - # If true, then any SSH connections made will enable agent forwarding. - # Default value: false - # config.ssh.forward_agent = true - # Share an additional folder to the guest VM. The first argument is # the path on the host to the actual folder. The second argument is # the path on the guest to mount the folder. And the optional third @@ -52,77 +50,28 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| # Example for VirtualBox: # # config.vm.provider "virtualbox" do |vb| - # # Don't boot with headless mode + # # Display the VirtualBox GUI when booting the machine # vb.gui = true # - # # Use VBoxManage to customize the VM. For example to change memory: - # vb.customize ["modifyvm", :id, "--memory", "1024"] + # # Customize the amount of memory on the VM: + # vb.memory = "1024" # end # - # View the documentation for the provider you're using for more + # View the documentation for the provider you are using for more # information on available options. - # Enable provisioning with CFEngine. CFEngine Community packages are - # automatically installed. For example, configure the host as a - # policy server and optionally a policy file to run: - # - # config.vm.provision "cfengine" do |cf| - # cf.am_policy_hub = true - # # cf.run_file = "motd.cf" - # end - # - # You can also configure and bootstrap a client to an existing - # policy server: - # - # config.vm.provision "cfengine" do |cf| - # cf.policy_server_address = "10.0.2.15" + # Define a Vagrant Push strategy for pushing to Atlas. Other push strategies + # such as FTP and Heroku are also available. See the documentation at + # https://docs.vagrantup.com/v2/push/atlas.html for more information. + # config.push.define "atlas" do |push| + # push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME" # end - # Enable provisioning with Puppet stand alone. Puppet manifests - # are contained in a directory path relative to this Vagrantfile. - # You will need to create the manifests directory and a manifest in - # the file default.pp in the manifests_path directory. - # - # config.vm.provision "puppet" do |puppet| - # puppet.manifests_path = "manifests" - # puppet.manifest_file = "default.pp" - # end - - # Enable provisioning with chef solo, specifying a cookbooks path, roles - # path, and data_bags path (all relative to this Vagrantfile), and adding - # some recipes and/or roles. - # - # config.vm.provision "chef_solo" do |chef| - # chef.cookbooks_path = "../my-recipes/cookbooks" - # chef.roles_path = "../my-recipes/roles" - # chef.data_bags_path = "../my-recipes/data_bags" - # chef.add_recipe "mysql" - # chef.add_role "web" - # - # # You may also specify custom JSON attributes: - # chef.json = { mysql_password: "foo" } - # end - - # Enable provisioning with chef server, specifying the chef server URL, - # and the path to the validation key (relative to this Vagrantfile). - # - # The Opscode Platform uses HTTPS. Substitute your organization for - # ORGNAME in the URL and validation key. - # - # If you have your own Chef Server, use the appropriate URL, which may be - # HTTP instead of HTTPS depending on your configuration. Also change the - # validation key to validation.pem. - # - # config.vm.provision "chef_client" do |chef| - # chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME" - # chef.validation_key_path = "ORGNAME-validator.pem" - # end - # - # If you're using the Opscode platform, your validator client is - # ORGNAME-validator, replacing ORGNAME with your organization name. - # - # If you have your own Chef Server, the default validation client name is - # chef-validator, unless you changed the configuration. - # - # chef.validation_client_name = "ORGNAME-validator" + # Enable provisioning with a shell script. Additional provisioners such as + # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the + # documentation for more information about their specific syntax and use. + # config.vm.provision "shell", inline: <<-SHELL + # sudo apt-get update + # sudo apt-get install -y apache2 + # SHELL end diff --git a/templates/commands/init/Vagrantfile.min.erb b/templates/commands/init/Vagrantfile.min.erb index a1f886d47..ac70f5e7c 100644 --- a/templates/commands/init/Vagrantfile.min.erb +++ b/templates/commands/init/Vagrantfile.min.erb @@ -1,10 +1,4 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : - -# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! -VAGRANTFILE_API_VERSION = "2" - -Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| +Vagrant.configure(2) do |config| config.vm.box = "<%= box_name %>" <% if box_url -%> config.vm.box_url = "<%= box_url %>" diff --git a/templates/guests/arch/network_dhcp.erb b/templates/guests/arch/network_dhcp.erb index cea4e8587..377784574 100644 --- a/templates/guests/arch/network_dhcp.erb +++ b/templates/guests/arch/network_dhcp.erb @@ -1,4 +1,4 @@ Description='A basic dhcp ethernet connection' -Interface=eth<%= options[:interface] %> +Interface=<%= options[:device] %> Connection=ethernet IP=dhcp diff --git a/templates/guests/arch/network_static.erb b/templates/guests/arch/network_static.erb index 6cf2b9a62..f8cb06f1b 100644 --- a/templates/guests/arch/network_static.erb +++ b/templates/guests/arch/network_static.erb @@ -1,5 +1,8 @@ Connection=ethernet Description='A basic static ethernet connection' -Interface=eth<%= options[:interface] %> +Interface=<%= options[:device] %> IP=static Address=('<%= options[:ip]%>/24') +<% if options[:gateway] %> +Gateway='<%= options[:gateway] %>' +<% end %> diff --git a/templates/guests/debian/network_dhcp.erb b/templates/guests/debian/network_dhcp.erb index 5a4ed783c..4dc4f8672 100644 --- a/templates/guests/debian/network_dhcp.erb +++ b/templates/guests/debian/network_dhcp.erb @@ -6,7 +6,7 @@ iface eth<%= options[:interface] %> inet dhcp post-up route del default dev $IFACE || true <% else %> # We need to disable eth0, see GH-2648 - post-up route del default dev eth0 + post-up route del default dev eth0 || true post-up dhclient $IFACE pre-down route add default dev eth0 <% end %> diff --git a/templates/guests/debian/network_static.erb b/templates/guests/debian/network_static.erb index fa505afa9..91f0cd62e 100644 --- a/templates/guests/debian/network_static.erb +++ b/templates/guests/debian/network_static.erb @@ -4,4 +4,7 @@ auto eth<%= options[:interface] %> iface eth<%= options[:interface] %> inet static address <%= options[:ip] %> netmask <%= options[:netmask] %> +<% if options[:gateway] %> + gateway <%= options[:gateway] %> +<% end %> #VAGRANT-END diff --git a/templates/guests/fedora/network_static.erb b/templates/guests/fedora/network_static.erb index dc8e85c1e..000ea6150 100644 --- a/templates/guests/fedora/network_static.erb +++ b/templates/guests/fedora/network_static.erb @@ -6,7 +6,11 @@ ONBOOT=yes IPADDR=<%= options[:ip] %> NETMASK=<%= options[:netmask] %> DEVICE=<%= options[:device] %> -<%= options[:gateway] ? "GATEWAY=#{options[:gateway]}" : '' %> -<%= options[:mac_address] ? "HWADDR=#{options[:mac_address]}" : '' %> +<% if options[:gateway] %> +GATEWAY=<%= options[:gateway] %> +<% end %> +<% if options[:mac_address] %> +HWADDR=<%= options[:mac_address] %> +<% end %> PEERDNS=no #VAGRANT-END diff --git a/templates/guests/freebsd/network_static.erb b/templates/guests/freebsd/network_static.erb index 0edc518cb..e73c7c124 100644 --- a/templates/guests/freebsd/network_static.erb +++ b/templates/guests/freebsd/network_static.erb @@ -1,3 +1,6 @@ #VAGRANT-BEGIN ifconfig_<%= ifname %>="inet <%= options[:ip] %> netmask <%= options[:netmask] %>" +<% if options[:gateway] %> +default_router="<%= options[:gateway] %>" +<% end %> #VAGRANT-END diff --git a/templates/guests/funtoo/network_static.erb b/templates/guests/funtoo/network_static.erb index ab6a09d10..001df7419 100644 --- a/templates/guests/funtoo/network_static.erb +++ b/templates/guests/funtoo/network_static.erb @@ -2,5 +2,9 @@ # The contents below are automatically generated by Vagrant. Do not modify. template='interface' ipaddr='<%= options[:ip] %>/<%= options[:netmask] %>' -<%= [:gateway, :nameservers, :domain, :route, :gateway6, :route6, :mtu].reduce([]) { |a,i| a.push("#{i}=#{options[i]}") if options[i]; a }.join("\n") %> +<% [:gateway, :nameservers, :domain, :route, :gateway6, :route6, :mtu].each do |key| %> +<% if options[key] %> +<%= key %>='<%= options[key] %>' +<% end %> +<% end %> #VAGRANT-END diff --git a/templates/guests/gentoo/network_static.erb b/templates/guests/gentoo/network_static.erb index 0df432cb0..fe6c77194 100644 --- a/templates/guests/gentoo/network_static.erb +++ b/templates/guests/gentoo/network_static.erb @@ -1,4 +1,7 @@ #VAGRANT-BEGIN # The contents below are automatically generated by Vagrant. Do not modify. config_eth<%= options[:interface] %>=("<%= options[:ip] %> netmask <%= options[:netmask] %>") +<% if options[:gateway] %> +gateways_eth<%= options[:interface] %>="<%= options[:gateway] %>" +<% end %> #VAGRANT-END diff --git a/templates/guests/redhat/network_static.erb b/templates/guests/redhat/network_static.erb index c28dd74cb..53ef59571 100644 --- a/templates/guests/redhat/network_static.erb +++ b/templates/guests/redhat/network_static.erb @@ -6,5 +6,8 @@ ONBOOT=yes IPADDR=<%= options[:ip] %> NETMASK=<%= options[:netmask] %> DEVICE=eth<%= options[:interface] %> +<% if options[:gateway] %> +GATEWAY=<%= options[:gateway] %> +<% end %> PEERDNS=no #VAGRANT-END diff --git a/templates/guests/suse/network_static.erb b/templates/guests/suse/network_static.erb index 7388fc355..c9270d7cd 100644 --- a/templates/guests/suse/network_static.erb +++ b/templates/guests/suse/network_static.erb @@ -4,7 +4,10 @@ BOOTPROTO='static' IPADDR='<%= options[:ip] %>' NETMASK='<%= options[:netmask] %>' DEVICE='eth<%= options[:interface] %>' -PEERDNS=no +<% if options[:gateway] %> +GATEWAY='<%= options[:gateway] %>' +<% end %> +PEERDNS='no' STARTMODE='auto' USERCONTROL='no' #VAGRANT-END diff --git a/templates/locales/comm_winrm.yml b/templates/locales/comm_winrm.yml index 9de47a9cf..0f01a2755 100644 --- a/templates/locales/comm_winrm.yml +++ b/templates/locales/comm_winrm.yml @@ -1,11 +1,10 @@ en: vagrant_winrm: errors: - auth_error: |- + authentication_failed: |- An authorization error occurred while connecting to WinRM. User: %{user} - Password: %{password} Endpoint: %{endpoint} Message: %{message} winrm_bad_exit_status: |- @@ -29,6 +28,15 @@ en: Message: %{message} invalid_shell: |- %{shell} is not a supported type of Windows shell. + invalid_transport: |- + %{transport} is not a supported WinRM transport. + ssl_error: |- + An SSL error occurred while connecting to WinRM. This usually + occurs when you are using a self-signed certificate and have + not set the WinRM `ssl_peer_verification` config setting to false. + + Message: %{message} + winrm_not_ready: |- The box is not able to report an address for WinRM to connect to yet. WinRM cannot access this Vagrant environment. Please wait for the @@ -39,3 +47,36 @@ en: From: %{from} To: %{to} Message: %{message} + + connection_refused: |- + WinRM connection was refused! This usually happens if the VM failed to + boot properly. Some steps to try to fix this: First, try reloading your + VM with `vagrant reload`, since a simple restart sometimes fixes things. + If that doesn't work, destroy your VM and recreate it with a `vagrant destroy` + followed by a `vagrant up`. If that doesn't work, contact a Vagrant + maintainer (support channels listed on the website) for more assistance. + connection_reset: |- + WinRM connection was reset! This usually happens when the machine is + taking too long to reboot. First, try reloading your machine with + `vagrant reload`, since a simple restart sometimes fixes things. + If that doesn't work, destroy your machine and recreate it with + a `vagrant destroy` followed by a `vagrant up`. If that doesn't work, + contact support. + connection_timeout: |- + Vagrant timed out while attempting to connect via WinRM. This usually + means that the VM booted, but there are issues with the WinRM configuration + or network connectivity issues. Please try to `vagrant reload` or + `vagrant up` again. + disconnected: |- + The WinRM connection was unexpectedly closed by the remote end. This + usually indicates that WinRM within the guest machine was unable to + properly start up. Please boot the VM in GUI mode to check whether + it is booting properly. + no_route: |- + While attempting to connect with WinRM, a "no route to host" (EHOSTUNREACH) + error was received. Please verify your network settings are correct + and try again. + host_down: |- + While attempting to connect with WinRM, a "host is down" (EHOSTDOWN) + error was received. Please verify your WinRM settings are correct + and try again. diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 4cbf5a0a3..c5edb63a1 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -20,10 +20,14 @@ en: Adding box '%{name}' (v%{version}) for provider: %{providers} box_added: |- Successfully added box '%{name}' (v%{version}) for '%{provider}'! + box_adding_direct: |- + Box file was not detected as metadata. Adding it directly... box_downloading: |- Downloading: %{url} box_download_error: |- Error downloading: %{message} + box_unpacking: |- + Unpacking necessary files from: %{url} box_expanding_url: |- URL: %{url} box_loading_metadata: |- @@ -87,6 +91,14 @@ en: CFEngine running in "single run" mode. Will execute one file. cfengine_single_run_execute: |- Executing run file for CFEngine... + chef_cant_detect: |- + Vagrant does not support detecting whether Chef is installed + for the guest OS running in the machine. Vagrant will assume it is + installed and attempt to continue. + chef_already_installed: |- + Detected Chef (%{version}) is already installed + chef_installing: |- + Installing Chef (%{version})... chef_client_cleanup_failed: |- Cleaning up the '%{deletable}' for Chef failed. The stdout and stderr are shown below. Vagrant will continue destroying the machine, @@ -112,8 +124,11 @@ en: docker_configure_autostart: |- Configuring Docker to autostart containers... docker_install_with_version_not_supported: |- - Vagrant is not capable of installing an specific version of Docker + Vagrant is not capable of installing a specific version of Docker onto the guest machine and the latest version will be installed. + This is a limitation of Vagrant knowing how to interact with this + operating system. This isn't a bug, but if you know how to fix this + please report it to Vagrant. docker_installing: |- Installing Docker (%{version}) onto machine... docker_pulling_images: @@ -126,12 +141,19 @@ en: Building Docker images... docker_running: |- -- Container: %{name} - docker_starting_containers: + docker_restarting_container: |- + -- Detected changes to container '%{name}', restarting... + docker_starting_containers: |- Starting Docker containers... inserted_key: |- Key inserted! Disconnecting and reconnecting using new SSH key... - inserting_insecure_key: |- - Inserting Vagrant public key within guest... + inserting_insecure_detected: |- + Vagrant insecure key detected. Vagrant will automatically replace + this with a newly generated keypair for better security. + inserting_random_key: |- + Inserting generated public key within guest... + inserting_remove_key: |- + Removing insecure key from the guest if it's present... list_commands: |- Below is a listing of all available Vagrant commands and a brief description of what they do. @@ -184,6 +206,12 @@ en: have specified `rsync_auto` to be false. rsync_auto_path: |- Watching: %{path} + rsync_auto_rsync_error: |- + There was an error while executing rsync. The error is shown below. + This may not be critical since rsync sometimes fails, but if this message + repeats, then please fix the issue: + + %{message} rsync_communicator_not_ready: |- The machine is reporting that it is not ready for rsync to communicate with it. Verify that this machine is properly running. @@ -195,6 +223,11 @@ en: Rsyncing folder: %{hostpath} => %{guestpath} rsync_folder_excludes: " - Exclude: %{excludes}" rsync_installing: "Installing rsync to the VM..." + rsync_proxy_machine: |- + The provider ('%{provider}') for the machine '%{name}' is + using a proxy machine. RSync will sync to this proxy + instead of directly to the environment itself. + rsync_showing_output: "Showing rsync output..." rsync_ssh_password: |- The machine you're rsyncing folders to is configured to use password-based authentication. Vagrant can't script rsync to automatically @@ -212,6 +245,10 @@ en: Error! Your console doesn't support hiding input. We'll ask for input again below, but we WILL NOT be able to hide input. If this is a problem for you, ctrl-C to exit and fix your stdin. + up_no_machines: |- + No machines to bring up. This is usually because all machines are + set to `autostart: false`, which means you have to explicitly specify + the name of the machine to bring up. upgrading_home_path_v1_5: |- Vagrant is upgrading some internal state for the latest version. Please do not quit Vagrant at this time. While upgrading, Vagrant @@ -222,7 +259,7 @@ en: Press ctrl-c now to exit if you want to remove some boxes or free up some disk space. - Press any other key to continue. + Press the Enter or Return key to continue. version_current: |- Installed Version: %{version} version_latest: |- @@ -264,7 +301,7 @@ en: id_in_pre_import: |- The ':id' parameter is not available in "pre-import" customizations. intnet_on_bad_type: |- - VirtualBox internal networks can only be enabled on "private_networks" + VirtualBox internal networks can only be enabled on "private_network" invalid_event: |- %{event} is not a valid event for customization. Valid events are: %{valid_events} @@ -348,6 +385,9 @@ en: provider. Double-check your requested provider to verify you didn't simply misspell it. + If you're adding a box from HashiCorp's Atlas, make sure the box is + released. + Name: %{name} Address: %{url} Requested provider: %{requested} @@ -364,7 +404,7 @@ en: box_add_short_not_found: |- The box '%{name}' could not be found or could not be accessed in the remote catalog. If this is a private - box on Vagrant Cloud, please verify you're logged in via + box on HashiCorp's Atlas, please verify you're logged in via `vagrant login`. Also, please double-check the name. The expanded URL and error message are shown below: @@ -381,6 +421,9 @@ en: GUI often has more helpful error messages than Vagrant can retrieve. For example, if you're using VirtualBox, run `vagrant up` while the VirtualBox GUI is open. + + The primary issue for this error is that the provider you're using + is not properly configured. This is very rarely a Vagrant issue. boot_timeout: |- Timed out while waiting for the machine to boot. This means that Vagrant was unable to communicate with the guest machine within @@ -526,16 +569,14 @@ en: %{versions} box_server_not_set: |- - A URL to a Vagrant Cloud server is not set, so boxes cannot - be added with a shorthand ("mitchellh/precise64") format. - You may also be seeing this error if you meant to type in - a path to a box file which doesn't exist locally on your - system. + A URL to an Atlas server is not set, so boxes cannot be added with a + shorthand ("mitchellh/precise64") format. You may also be seeing this + error if you meant to type in a path to a box file which doesn't exist + locally on your system. - To set a URL to a Vagrant Cloud server, set the - `VAGRANT_SERVER_URL` environmental variable. Or, if you - meant to use a file path, make sure the path to the file - is valid. + To set a URL to an Atlas server, set the `VAGRANT_SERVER_URL` + environmental variable. Or, if you meant to use a file path, make sure + the path to the file is valid. box_update_multi_provider: |- You requested to update the box '%{name}'. This box has multiple providers. You must explicitly select a single @@ -850,6 +891,21 @@ en: If this message is erroneous, you may disable this check by setting `config.nfs.verify_installed` to `false` in your Vagrantfile. + no_default_provider: |- + No usable default provider could be found for your system. + + Vagrant relies on interactions with 3rd party systems, known as + "providers", to provide Vagrant with resources to run development + environments. Examples are VirtualBox, VMware, Hyper-V. + + The easiest solution to this message is to install VirtualBox, which + is available for free on all major platforms. + + If you believe you already have a provider available, make sure it + is properly installed and configured. You can see more details about + why a particular provider isn't working by forcing usage with + `vagrant up --provider=PROVIDER`, which should give you a more specific + error message for that particular provider. no_default_synced_folder_impl: |- No synced folder implementation is available for your synced folders! Please consult the documentation to learn why this may be the case. @@ -907,6 +963,29 @@ en: You can however, install a plugin with the same name to replace these plugins. User-installed plugins take priority over system-installed plugins. + pushes_not_defined: |- + The Vagrantfile does not define any 'push' strategies. In order to use + `vagrant push`, you must define at least one push strategy: + + config.push.define "ftp" do |push| + # ... push-specific options + end + push_strategy_not_defined: |- + The push strategy '%{name}' is not defined in the Vagrantfile. Defined + strategy names are: + + %{pushes} + push_strategy_not_loaded: |- + There are no push strategies named '%{name}'. Please make sure you + spelled it correctly. If you are using an external push strategy, you + may need to install a plugin. Loaded push strategies are: + + %{pushes} + push_strategy_not_provided: |- + The Vagrantfile defines more than one 'push' strategy. Please specify a + strategy. Defined strategy names are: + + %{pushes} package_include_symlink: |- A file or directory you're attempting to include with your packaged box has symlinks in it. Vagrant cannot include symlinks in the @@ -1045,6 +1124,16 @@ en: using a shell that is unavailable on the system. Please verify you're using the full path to the shell and that the shell is executable by the SSH user. + ssh_insert_key_unsupported: |- + Vagrant is configured to generate a random keypair and insert it + onto the guest machine, but it appears Vagrant doesn't know how to do + this with your guest OS. Please disable key insertion by setting + `config.ssh.insert_key = false` in the Vagrantfile. + + After doing so, run `vagrant reload` for the setting to take effect. + + If you'd like Vagrant to learn how to insert keys on this OS, please + open an issue with details about your environment. ssh_is_putty_link: |- The `ssh` executable found in the PATH is a PuTTY Link SSH client. Vagrant is only compatible with OpenSSH SSH clients. Please install @@ -1069,6 +1158,13 @@ en: permissions on the following file to 0600 and then try running this command again: %{key_path} + + Note that this error occurs after Vagrant automatically tries to + do this for you. The likely cause of this error is a lack of filesystem + permissions or even filesystem functionality. For example, if your + Vagrant data is on a USB stick, a common case is that chmod is + not supported. The key will need to be moved to a filesystem that + supports chmod. ssh_key_type_not_supported: |- The private key you're attempting to use with this Vagrant box uses an unsupported encryption type. The SSH library Vagrant uses does not support @@ -1127,7 +1223,8 @@ en: a syntax error. Path: %{path} - Message: %{message} + Line number: %{line} + Message: %{exception_class}: %{message} vagrantfile_syntax_error: |- There is a syntax error in the following Vagrantfile. The syntax error message is reproduced below for convenience: @@ -1157,6 +1254,13 @@ en: Command: %{command} Stderr: %{stderr} + vboxmanage_launch_error: |- + There was an error running VBoxManage. This is usually a permissions + problem or installation problem with VirtualBox itself, and not Vagrant. + Please note the error message below (if any), resolve the issue, and + try Vagrant again. + + %{message} vboxmanage_not_found_error: |- The "VBoxManage" command or one of its dependencies could not be found. Please verify VirtualBox is properly installed. You can verify @@ -1190,6 +1294,18 @@ en: VirtualBox is complaining that the installation is incomplete. Please run `VBoxManage --version` to see the error message which should contain instructions on how to fix this error. + virtualbox_name_exists: |- + The name of your virtual machine couldn't be set because VirtualBox + is reporting another VM with that name already exists. Most of the + time, this is because of an error with VirtualBox not cleaning up + properly. To fix this, verify that no VMs with that name do exist + (by opening the VirtualBox GUI). If they don't, then look at the + folder in the error message from VirtualBox below and remove it + if there isn't any information you need in there. + + VirtualBox error: + + %{stderr} virtualbox_no_name: |- Vagrant was unable to determine the recommended name for your VirtualBox VM. This is usually an issue with VirtualBox. The output @@ -1212,6 +1328,23 @@ en: Vagrant uses the `VBoxManage` binary that ships with VirtualBox, and requires this to be available on the PATH. If VirtualBox is installed, please find the `VBoxManage` binary and add it to the PATH environmental variable. + virtualbox_user_mismatch: |- + The VirtualBox VM was created with a user that doesn't match the + current user running Vagrant. VirtualBox requires that the same user + be used to manage the VM that was created. Please re-run Vagrant with + that user. This is not a Vagrant issue. + + The UID used to create the VM was: %{original_uid} + Your UID is: %{uid} + virtualbox_version_empty: |- + Vagrant detected that VirtualBox appears installed on your system, + but calls to detect the version are returning empty. This is often + indicative of installation issues with VirtualBox. Please verify + that VirtualBox is properly installed. As a final verification, + please run the following command manually and verify a version is + outputted: + + %{vboxmanage} --version vm_creation_required: |- VM must be created before running this command. Run `vagrant up` first. vm_inaccessible: |- @@ -1248,8 +1381,8 @@ en: Cookbook path doesn't exist: %{path} custom_config_path_missing: |- Path specified for "custom_config_path" does not exist. - server_url_empty: "Chef server URL must be populated." - validation_key_path: "Validation key path must be valid path to your chef server validation key." + server_url_empty: "Chef Server URL must be populated." + validation_key_path: "Validation key path must be valid path to your Chef Server validation key." loader: bad_v1_key: |- Unknown configuration section '%{key}'. If this section was part of @@ -1447,6 +1580,9 @@ en: Enabling bridged network... preparing: |- Preparing bridged networking... + choice_help: |- + When choosing an interface, it is usually the one that is + being used to connect to the internet. specific_not_found: |- Specific bridge '%{bridge}' not found. You may be asked to specify which network to bridge to. @@ -1587,6 +1723,8 @@ en: using the other host only network. preparing: |- Preparing network interfaces based on configuration... + cleanup_vbox_default_dhcp: |- + Found default DHCP server from initial VirtualBox install. Cleaning it up... host_only_network: collides: |- The specified host network collides with a non-hostonly network! @@ -1608,10 +1746,10 @@ en: provision: beginning: "Running provisioner: %{provisioner}..." disabled_by_config: |- - Machine not provisioning because `--no-provision` is specified. + Machine not provisioned because `--no-provision` is specified. disabled_by_sentinel: |- Machine already provisioned. Run `vagrant provision` or use the `--provision` - to force provisioning. Provisioners marked to run always will still run. + flag to force provisioning. Provisioners marked to run always will still run. resume: resuming: Resuming suspended VM... unpausing: |- @@ -1661,7 +1799,7 @@ en: packaging: "Packaging additional file: %{file}" compressing: "Compressing package to: %{tar_path}" output_exists: |- - The specified file to save the package as already exists. Please + The specified file '%{file_name}' to save the package as already exists. Please remove this file or specify a different file name for outputting. output_is_directory: |- The specified output is a directory. Please specify a path including @@ -1697,12 +1835,32 @@ en: "The cookbook path '%{path}' doesn't exist. Ignoring..." json: "Generating chef JSON and uploading..." client_key_folder: "Creating folder to hold client key..." + install_failed: |- + Vagrant could not detect Chef on the guest! Even after Vagrant + attempted to install Chef, it could still not find Chef on the system. + Please make sure you are connected to the Internet and can access + Chef's package distribution servers. If you already have Chef + installed on this guest, you can disable the automatic Chef detection + by setting the 'install' option in the Chef configuration section of + your Vagrantfile: + + chef.install = false + log_level_empty: |- + The Chef provisioner requires a log level. If you did not set a + log level, this is probably a bug and should be reported. upload_validation_key: "Uploading chef client validation key..." upload_encrypted_data_bag_secret_key: "Uploading chef encrypted data bag secret key..." + recipe_empty: |- + Chef Apply provisioning requires that the `config.chef.recipe` be set + to a string containing the recipe contents you want to execute on the + guest. running_client: "Running chef-client..." running_client_again: "Running chef-client again (failed to converge)..." + running_apply: "Running chef-apply..." running_solo: "Running chef-solo..." running_solo_again: "Running chef-solo again (failed to converge)..." + running_zero: "Running chef-client (local-mode)..." + running_zero_again: "Running chef-client (local-mode) again (failed to converge)..." missing_shared_folders: |- Shared folders that Chef requires are missing on the virtual machine. This is usually due to configuration changing after already booting the @@ -1727,6 +1885,9 @@ en: server_validation_key_doesnt_exist: |- The validation key set for `config.chef.validation_key_path` does not exist! This file needs to exist so it can be uploaded to the virtual machine. + upload_path_empty: |- + The Chef Apply provisioner requires that the `config.chef.upload_path` + be set to a non-nil, non-empty value. deleting_from_server: "Deleting %{deletable} \"%{name}\" from Chef server..." file: @@ -1741,11 +1902,17 @@ en: installed on this guest. Puppet provisioning can not continue without Puppet properly installed. running_puppet: "Running Puppet with %{manifest}..." + running_puppet_env: "Running Puppet with environment %{environment}..." manifest_missing: |- The configured Puppet manifest is missing. Please specify a path to an existing manifest: %{manifest} + environment_missing: |- + The configured Puppet environment folder %{environment} was not found in the + specified environmentpath %{environmentpath}. + Please specify a path to an existing Puppet directory environment. + environment_path_missing: "The environment path specified for Puppet does not exist: %{path}" manifests_path_missing: "The manifests path specified for Puppet does not exist: %{path}" missing_shared_folders: |- Shared folders that Puppet requires are missing on the virtual machine. @@ -1781,6 +1948,7 @@ en: path_and_inline_set: "Only one of `path` or `inline` may be set." path_invalid: "`path` for shell provisioner does not exist on the host system: %{path}" running: "Running: %{script}" + runningas: "Running: %{local} as %{remote}" upload_path_not_set: "`upload_path` must be set for the shell provisioner." ansible: @@ -1796,10 +1964,16 @@ en: salt: minion_config_nonexist: |- - The specified minion_config file could not be found. + The specified minion_config '%{missing_config_file}' file could not be found. master_config_nonexist: |- - The specified master_config file could not be found. + The specified master_config '%{missing_config_file}' file could not be found. + grains_config_nonexist: |- + The specified grains_config file could not be found. missing_key: |- You must include both public and private keys. must_accept_keys: |- You must accept keys when running highstate with master! + + pushes: + file: + no_destination: "File destination must be specified." diff --git a/templates/locales/providers_docker.yml b/templates/locales/providers_docker.yml index ca128a7cc..bb414e359 100644 --- a/templates/locales/providers_docker.yml +++ b/templates/locales/providers_docker.yml @@ -12,6 +12,8 @@ en: Build image no longer exists. Rebuilding... building: |- Building the container from a Dockerfile... + building_named_dockerfile: |- + Building the container from the named Dockerfile: %{file}... creating: |- Creating the container... created: |- @@ -33,6 +35,8 @@ en: host. You'll see the output of the `vagrant up` for this VM below. host_machine_syncing_folders: |- Syncing folders to the host VM... + logging_in: |- + Logging in to Docker server... logs_host_state_unknown: |- This container requires a host VM, and the state of that VM is unknown. Run `vagrant up` to verify that the container and @@ -41,6 +45,8 @@ en: Container not created. Skipping. not_docker_provider: |- Not backed by Docker provider. Skipping. + pull: |- + Pulling image '%{image}'... run_command_required: |- `vagrant docker-run` requires a command to execute. This command must be specified after a `--` in the command line. This is used @@ -119,6 +125,8 @@ en: "build_dir" must exist and contain a Dockerfile build_dir_or_image: |- One of "build_dir" or "image" must be set + create_args_array: |- + "create_args" must be an array invalid_link: |- Invalid link (should be 'name:alias'): "%{link}" invalid_vagrantfile: |- @@ -151,6 +159,11 @@ en: is functional and properly configured. Host VM ID: %{id} + package_not_supported: |- + The "package" command is not supported with the Docker provider. + If you'd like to commit or push your Docker container, please SSH + into the host VM (if there is one), and run `docker commit` and + so on manually. state_not_running: |- The container never entered the "running" state, or entered it briefly but reverted back to another state. Please verify that @@ -176,7 +189,16 @@ en: d.remains_running = false end + suspend_not_supported: |- + The "suspend" command is not supported with the Docker provider. + Docker containers don't natively support suspend. If you're using + a host machine, you can suspend the host machine by finding it + in `vagrant global-status` and using `vagrant suspend `. synced_folder_non_docker: |- The "docker" synced folder type can't be used because the provider in use is not Docker. This synced folder type only works with the Docker provider. The provider this machine is using is: %{provider} + vagrantfile_not_found: |- + The configured host VM Vagrantfile could not be found. Please fix + the Vagrantfile for this Docker environment to point to a valid + host VM. diff --git a/templates/provisioners/chef_solo/solo.erb b/templates/provisioners/chef_solo/solo.erb index fd52fff91..25d3346b7 100644 --- a/templates/provisioners/chef_solo/solo.erb +++ b/templates/provisioners/chef_solo/solo.erb @@ -5,11 +5,7 @@ file_cache_path "<%= file_cache_path %>" file_backup_path "<%= file_backup_path %>" cookbook_path <%= cookbooks_path.inspect %> <% if roles_path %> -if Chef::VERSION.to_f < 11.8 - role_path <%= roles_path.first.inspect %> -else - role_path <%= roles_path.inspect %> -end +role_path <%= roles_path.size == 1 ? roles_path.first.inspect : roles_path.inspect %> <% end %> log_level <%= log_level.inspect %> verbose_logging <%= verbose_logging.inspect %> @@ -32,6 +28,13 @@ environment_path <%= environments_path.inspect %> environment "<%= environment %>" <% end -%> +<% if local_mode -%> +local_mode true +<% end -%> +<% if node_path -%> +node_path <%= node_path.inspect %> +<% end -%> + http_proxy <%= http_proxy.inspect %> http_proxy_user <%= http_proxy_user.inspect %> http_proxy_pass <%= http_proxy_pass.inspect %> diff --git a/templates/provisioners/chef_zero/zero.erb b/templates/provisioners/chef_zero/zero.erb new file mode 100644 index 000000000..29de30d23 --- /dev/null +++ b/templates/provisioners/chef_zero/zero.erb @@ -0,0 +1,44 @@ +<% if node_name %> +node_name "<%= node_name %>" +<% end %> +file_cache_path "<%= file_cache_path %>" +file_backup_path "<%= file_backup_path %>" +cookbook_path <%= cookbooks_path.inspect %> +<% if roles_path %> +role_path <%= roles_path.size == 1 ? roles_path.first.inspect : roles_path.inspect %> +<% end %> +log_level <%= log_level.inspect %> +verbose_logging <%= verbose_logging.inspect %> +<% if !enable_reporting %> +enable_reporting <%= enable_reporting.inspect %> +<% end %> + +encrypted_data_bag_secret <%= encrypted_data_bag_secret.inspect %> + +<% if data_bags_path -%> +data_bag_path <%= data_bags_path.inspect %> +<% end %> + +<% if environments_path %> +environment_path <%= environments_path.inspect %> +<% end -%> + +<% if environment %> +environment "<%= environment %>" +<% end -%> + +<% if local_mode -%> +chef_zero.enabled true +local_mode true +<% end -%> +<% if node_path -%> +node_path <%= node_path.inspect %> +<% end -%> + +<% if formatter %> +add_formatter "<%= formatter %>" +<% end %> + +<% if custom_configuration -%> +Chef::Config.from_file "<%= custom_configuration %>" +<% end -%> diff --git a/test/unit/base.rb b/test/unit/base.rb index 3020c7b91..e3c68c099 100644 --- a/test/unit/base.rb +++ b/test/unit/base.rb @@ -4,6 +4,7 @@ require "rubygems" # Gems require "checkpoint" require "rspec/autorun" +require "webmock/rspec" # Require Vagrant itself so we can reference the proper # classes to test. @@ -28,7 +29,6 @@ $stderr.sync = true # Configure RSpec RSpec.configure do |c| - c.expect_with :rspec, :stdlib c.treat_symbols_as_metadata_keys_with_true_values = true if Vagrant::Util::Platform.windows? diff --git a/test/unit/plugins/commands/login/client_test.rb b/test/unit/plugins/commands/login/client_test.rb new file mode 100644 index 000000000..f57670eb0 --- /dev/null +++ b/test/unit/plugins/commands/login/client_test.rb @@ -0,0 +1,130 @@ +require File.expand_path("../../../../base", __FILE__) + +require Vagrant.source_root.join("plugins/commands/login/command") + +describe VagrantPlugins::LoginCommand::Client do + include_context "unit" + + let(:env) { isolated_environment.create_vagrant_env } + + subject { described_class.new(env) } + + before do + stub_env("ATLAS_TOKEN" => nil) + subject.clear_token + end + + describe "#logged_in?" do + let(:url) { "#{Vagrant.server_url}/api/v1/authenticate?access_token=#{token}" } + let(:headers) { { "Content-Type" => "application/json" } } + + before { allow(subject).to receive(:token).and_return(token) } + + context "when there is no token" do + let(:token) { nil } + + it "returns false" do + expect(subject.logged_in?).to be(false) + end + end + + context "when there is a token" do + let(:token) { "ABCD1234" } + + it "returns true if the endpoint returns a 200" do + stub_request(:get, url) + .with(headers: headers) + .to_return(body: JSON.pretty_generate("token" => token)) + expect(subject.logged_in?).to be(true) + end + + it "returns false if the endpoint returns a non-200" do + stub_request(:get, url) + .with(headers: headers) + .to_return(body: JSON.pretty_generate("bad" => true), status: 401) + expect(subject.logged_in?).to be(false) + end + + it "raises an exception if the server cannot be found" do + stub_request(:get, url) + .to_raise(SocketError) + expect { subject.logged_in? } + .to raise_error(VagrantPlugins::LoginCommand::Errors::ServerUnreachable) + end + end + end + + describe "#login" do + it "returns the access token after successful login" do + request = { + "user" => { + "login" => "foo", + "password" => "bar", + }, + } + + response = { + "token" => "baz", + } + + headers = { "Content-Type" => "application/json" } + + stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). + with(body: JSON.dump(request), headers: headers). + to_return(status: 200, body: JSON.dump(response)) + + expect(subject.login("foo", "bar")).to eq("baz") + end + + it "returns nil on bad login" do + stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). + to_return(status: 401, body: "") + + expect(subject.login("foo", "bar")).to be(false) + end + + it "raises an exception if it can't reach the sever" do + stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). + to_raise(SocketError) + + expect { subject.login("foo", "bar") }. + to raise_error(VagrantPlugins::LoginCommand::Errors::ServerUnreachable) + end + end + + describe "#token" do + it "reads ATLAS_TOKEN" do + stub_env("ATLAS_TOKEN" => "ABCD1234") + expect(subject.token).to eq("ABCD1234") + end + + it "reads the stored file" do + subject.store_token("EFGH5678") + expect(subject.token).to eq("EFGH5678") + end + + it "prefers the environment variable" do + stub_env("ATLAS_TOKEN" => "ABCD1234") + subject.store_token("EFGH5678") + expect(subject.token).to eq("ABCD1234") + end + + it "returns nil if there's no token set" do + expect(subject.token).to be(nil) + end + end + + describe "#store_token, #clear_token" do + it "stores the token and can re-access it" do + subject.store_token("foo") + expect(subject.token).to eq("foo") + expect(described_class.new(env).token).to eq("foo") + end + + it "deletes the token" do + subject.store_token("foo") + subject.clear_token + expect(subject.token).to be_nil + end + end +end diff --git a/test/unit/plugins/commands/login/command_test.rb b/test/unit/plugins/commands/login/command_test.rb new file mode 100644 index 000000000..c92c51174 --- /dev/null +++ b/test/unit/plugins/commands/login/command_test.rb @@ -0,0 +1,96 @@ +require File.expand_path("../../../../base", __FILE__) + +require Vagrant.source_root.join("plugins/commands/login/command") + +describe VagrantPlugins::LoginCommand::Command do + include_context "unit" + + let(:env) { isolated_environment.create_vagrant_env } + + let(:token_path) { env.data_dir.join("vagrant_login_token") } + + let(:stdout) { StringIO.new } + let(:stderr) { StringIO.new } + + subject { described_class.new(argv, env) } + + before do + stub_env("ATLAS_TOKEN" => "") + end + + describe "#execute" do + context "with no args" do + let(:argv) { [] } + end + + context "with --check" do + let(:argv) { ["--check"] } + + context "when there is a token" do + before do + stub_request(:get, %r{^#{Vagrant.server_url}/api/v1/authenticate}) + .to_return(status: 200) + end + + before do + File.open(token_path, "w+") { |f| f.write("abcd1234") } + end + + it "returns 0" do + expect(subject.execute).to eq(0) + end + end + + context "when there is no token" do + it "returns 1" do + expect(subject.execute).to eq(1) + end + end + end + + context "with --logout" do + let(:argv) { ["--logout"] } + + it "returns 0" do + expect(subject.execute).to eq(0) + end + + it "clears the token" do + subject.execute + expect(File.exist?(token_path)).to be(false) + end + end + + context "with --token" do + let(:argv) { ["--token", "efgh5678"] } + + context "when the token is valid" do + before do + stub_request(:get, %r{^#{Vagrant.server_url}/api/v1/authenticate}) + .to_return(status: 200) + end + + it "sets the token" do + subject.execute + token = File.read(token_path).strip + expect(token).to eq("efgh5678") + end + + it "returns 0" do + expect(subject.execute).to eq(0) + end + end + + context "when the token is invalid" do + before do + stub_request(:get, %r{^#{Vagrant.server_url}/api/v1/authenticate}) + .to_return(status: 401) + end + + it "returns 1" do + expect(subject.execute).to eq(1) + end + end + end + end +end diff --git a/test/unit/plugins/commands/login/middleware/add_authentication_test.rb b/test/unit/plugins/commands/login/middleware/add_authentication_test.rb new file mode 100644 index 000000000..99c99a8af --- /dev/null +++ b/test/unit/plugins/commands/login/middleware/add_authentication_test.rb @@ -0,0 +1,88 @@ +require File.expand_path("../../../../../base", __FILE__) + +require Vagrant.source_root.join("plugins/commands/login/middleware/add_authentication") + +describe VagrantPlugins::LoginCommand::AddAuthentication do + include_context "unit" + + let(:app) { lambda { |env| } } + let(:env) { { + env: iso_env, + } } + + let(:iso_env) { isolated_environment.create_vagrant_env } + let(:server_url) { "http://foo.com" } + + subject { described_class.new(app, env) } + + before do + allow(Vagrant).to receive(:server_url).and_return(server_url) + stub_env("ATLAS_TOKEN" => nil) + end + + describe "#call" do + it "does nothing if we have no server set" do + allow(Vagrant).to receive(:server_url).and_return(nil) + VagrantPlugins::LoginCommand::Client.new(iso_env).store_token("foo") + + original = ["foo", "#{server_url}/bar"] + env[:box_urls] = original.dup + + subject.call(env) + + expect(env[:box_urls]).to eq(original) + end + + it "does nothing if we aren't logged in" do + original = ["foo", "#{server_url}/bar"] + env[:box_urls] = original.dup + + subject.call(env) + + expect(env[:box_urls]).to eq(original) + end + + it "appends the access token to the URL of server URLs" do + token = "foobarbaz" + VagrantPlugins::LoginCommand::Client.new(iso_env).store_token(token) + + original = [ + "http://google.com/box.box", + "#{server_url}/foo.box", + "#{server_url}/bar.box?arg=true", + ] + + expected = original.dup + expected[1] = "#{original[1]}?access_token=#{token}" + expected[2] = "#{original[2]}&access_token=#{token}" + + env[:box_urls] = original.dup + subject.call(env) + + expect(env[:box_urls]).to eq(expected) + end + + it "appends the access token to vagrantcloud.com URLs if Atlas" do + server_url = "https://atlas.hashicorp.com" + allow(Vagrant).to receive(:server_url).and_return(server_url) + + token = "foobarbaz" + VagrantPlugins::LoginCommand::Client.new(iso_env).store_token(token) + + original = [ + "http://google.com/box.box", + "http://vagrantcloud.com/foo.box", + "http://vagrantcloud.com/bar.box?arg=true", + ] + + expected = original.dup + expected[1] = "#{original[1]}?access_token=#{token}" + expected[2] = "#{original[2]}&access_token=#{token}" + + env[:box_urls] = original.dup + subject.call(env) + + expect(env[:box_urls]).to eq(expected) + end + end +end diff --git a/test/unit/plugins/commands/push/command_test.rb b/test/unit/plugins/commands/push/command_test.rb new file mode 100644 index 000000000..a97f4caba --- /dev/null +++ b/test/unit/plugins/commands/push/command_test.rb @@ -0,0 +1,141 @@ +require File.expand_path("../../../../base", __FILE__) + +require Vagrant.source_root.join("plugins/commands/push/command") + +describe VagrantPlugins::CommandPush::Command do + include_context "unit" + include_context "command plugin helpers" + + let(:iso_env) { isolated_environment } + let(:env) do + iso_env.vagrantfile(<<-VF) + Vagrant.configure("2") do |config| + config.vm.box = "hashicorp/precise64" + end + VF + iso_env.create_vagrant_env + end + + let(:argv) { [] } + let(:pushes) { {} } + + subject { described_class.new(argv, env) } + + before do + Vagrant.plugin("2").manager.stub(pushes: pushes) + end + + describe "#execute" do + before do + allow(subject).to receive(:validate_pushes!) + .and_return(:noop) + allow(env).to receive(:pushes) + allow(env).to receive(:push) + end + + it "validates the pushes" do + expect(subject).to receive(:validate_pushes!).once + subject.execute + end + + it "delegates to Environment#push" do + expect(env).to receive(:push).once + subject.execute + end + + it "validates the configuration" do + iso_env.vagrantfile <<-EOH + Vagrant.configure("2") do |config| + config.vm.box = "hashicorp/precise64" + + config.push.define "noop" do |push| + push.bad = "ham" + end + end + EOH + + subject = described_class.new(argv, iso_env.create_vagrant_env) + allow(subject).to receive(:validate_pushes!) + .and_return(:noop) + + expect { subject.execute }.to raise_error(Vagrant::Errors::ConfigInvalid) { |err| + expect(err.message).to include("The following settings shouldn't exist: bad") + } + end + end + + describe "#validate_pushes!" do + context "when there are no pushes defined" do + let(:pushes) { [] } + + context "when a strategy is given" do + it "raises an exception" do + expect { subject.validate_pushes!(pushes, :noop) } + .to raise_error(Vagrant::Errors::PushesNotDefined) + end + end + + context "when no strategy is given" do + it "raises an exception" do + expect { subject.validate_pushes!(pushes) } + .to raise_error(Vagrant::Errors::PushesNotDefined) + end + end + end + + context "when there is one push defined" do + let(:noop) { double("noop") } + let(:pushes) { [:noop] } + + context "when a strategy is given" do + context "when that strategy is not defined" do + it "raises an exception" do + expect { subject.validate_pushes!(pushes, :bacon) } + .to raise_error(Vagrant::Errors::PushStrategyNotDefined) + end + end + + context "when that strategy is defined" do + it "returns that push" do + expect(subject.validate_pushes!(pushes, :noop)).to eq(:noop) + end + end + end + + context "when no strategy is given" do + it "returns the strategy" do + expect(subject.validate_pushes!(pushes)).to eq(:noop) + end + end + end + + context "when there are multiple pushes defined" do + let(:noop) { double("noop") } + let(:ftp) { double("ftp") } + let(:pushes) { [:noop, :ftp] } + + context "when a strategy is given" do + context "when that strategy is not defined" do + it "raises an exception" do + expect { subject.validate_pushes!(pushes, :bacon) } + .to raise_error(Vagrant::Errors::PushStrategyNotDefined) + end + end + + context "when that strategy is defined" do + it "returns the strategy" do + expect(subject.validate_pushes!(pushes, :noop)).to eq(:noop) + expect(subject.validate_pushes!(pushes, :ftp)).to eq(:ftp) + end + end + end + + context "when no strategy is given" do + it "raises an exception" do + expect { subject.validate_pushes!(pushes) } + .to raise_error(Vagrant::Errors::PushStrategyNotProvided) + end + end + end + end +end diff --git a/test/unit/plugins/communicators/winrm/communicator_test.rb b/test/unit/plugins/communicators/winrm/communicator_test.rb index 1433f4317..ceea0985d 100644 --- a/test/unit/plugins/communicators/winrm/communicator_test.rb +++ b/test/unit/plugins/communicators/winrm/communicator_test.rb @@ -28,11 +28,16 @@ describe VagrantPlugins::CommunicatorWinRM::Communicator do expect(subject.ready?).to be_true end - it "returns false if hostname command fails to execute without error" do - expect(shell).to receive(:powershell).with("hostname").and_raise(Vagrant::Errors::VagrantError) + it "returns false if hostname command fails with a transient error" do + expect(shell).to receive(:powershell).with("hostname").and_raise(VagrantPlugins::CommunicatorWinRM::Errors::TransientError) expect(subject.ready?).to be_false end + it "raises an error if hostname command fails with an unknown error" do + expect(shell).to receive(:powershell).with("hostname").and_raise(Vagrant::Errors::VagrantError) + expect { subject.ready? }.to raise_error(Vagrant::Errors::VagrantError) + end + it "raises timeout error when hostname command takes longer then winrm timeout" do expect(shell).to receive(:powershell).with("hostname") do sleep 2 # winrm.timeout = 1 @@ -50,7 +55,8 @@ describe VagrantPlugins::CommunicatorWinRM::Communicator do it "wraps command in elevated shell script when elevated is true" do expect(shell).to receive(:upload).with(kind_of(String), "c:/tmp/vagrant-elevated-shell.ps1") expect(shell).to receive(:powershell) do |cmd| - expect(cmd).to eq("powershell -executionpolicy bypass -file c:/tmp/vagrant-elevated-shell.ps1") + expect(cmd).to eq("powershell -executionpolicy bypass -file \"c:/tmp/vagrant-elevated-shell.ps1\" " + + "-username \"vagrant\" -password \"password\" -encoded_command \"ZABpAHIAOwAgAGUAeABpAHQAIAAkAEwAQQBTAFQARQBYAEkAVABDAE8ARABFAA==\"") end.and_return({ exitcode: 0 }) expect(subject.execute("dir", { elevated: true })).to eq(0) end @@ -75,15 +81,23 @@ describe VagrantPlugins::CommunicatorWinRM::Communicator do describe ".test" do it "returns true when exit code is zero" do - expect(shell).to receive(:powershell).with(kind_of(String)).and_return({ exitcode: 0 }) + output = { exitcode: 0, data:[{ stderr: '' }] } + expect(shell).to receive(:powershell).with(kind_of(String)).and_return(output) expect(subject.test("test -d c:/windows")).to be_true end it "returns false when exit code is non-zero" do - expect(shell).to receive(:powershell).with(kind_of(String)).and_return({ exitcode: 1 }) + output = { exitcode: 1, data:[{ stderr: '' }] } + expect(shell).to receive(:powershell).with(kind_of(String)).and_return(output) expect(subject.test("test -d /tmp/foobar")).to be_false end + it "returns false when stderr contains output" do + output = { exitcode: 0, data:[{ stderr: 'this is an error' }] } + expect(shell).to receive(:powershell).with(kind_of(String)).and_return(output) + expect(subject.test("[-x stuff] && foo")).to be_false + end + it "returns false when command is testing for linux OS" do expect(subject.test("uname -s | grep Debian")).to be_false end @@ -101,6 +115,6 @@ describe VagrantPlugins::CommunicatorWinRM::Communicator do expect(shell).to receive(:download).with("from", "to") subject.download("from", "to") end - end + end end diff --git a/test/unit/plugins/communicators/winrm/shell_test.rb b/test/unit/plugins/communicators/winrm/shell_test.rb index 477108b97..de7b1dc19 100644 --- a/test/unit/plugins/communicators/winrm/shell_test.rb +++ b/test/unit/plugins/communicators/winrm/shell_test.rb @@ -1,14 +1,25 @@ require File.expand_path("../../../../base", __FILE__) require Vagrant.source_root.join("plugins/communicators/winrm/shell") +require Vagrant.source_root.join("plugins/communicators/winrm/config") describe VagrantPlugins::CommunicatorWinRM::WinRMShell do include_context "unit" let(:session) { double("winrm_session") } + let(:port) { config.transport == :ssl ? 5986 : 5985 } + let(:config) { + VagrantPlugins::CommunicatorWinRM::Config.new.tap do |c| + c.username = 'username' + c.password = 'password' + c.max_tries = 3 + c.retry_delay = 0 + c.finalize! + end + } subject do - described_class.new('localhost', 'username', 'password').tap do |comm| + described_class.new('localhost', port, config).tap do |comm| allow(comm).to receive(:new_session).and_return(session) end end @@ -19,11 +30,15 @@ describe VagrantPlugins::CommunicatorWinRM::WinRMShell do expect(subject.powershell("dir")[:exitcode]).to eq(0) end - it "should raise auth error when exception message contains 401" do - expect(session).to receive(:powershell).with(/^dir.+/).and_raise( - StandardError.new("Oh no! a 401 SOAP error!")) + it "should retry when a WinRMAuthorizationError is received" do + expect(session).to receive(:powershell).with(/^dir.+/).exactly(3).times.and_raise( + # Note: The initialize for WinRMAuthorizationError may require a status_code as + # the second argument in a future WinRM release. Currently it doesn't track the + # status code. + WinRM::WinRMAuthorizationError.new("Oh no!! Unauthrorized") + ) expect { subject.powershell("dir") }.to raise_error( - VagrantPlugins::CommunicatorWinRM::Errors::AuthError) + VagrantPlugins::CommunicatorWinRM::Errors::AuthenticationFailed) end it "should raise an execution error when an exception occurs" do @@ -42,8 +57,22 @@ describe VagrantPlugins::CommunicatorWinRM::WinRMShell do end describe ".endpoint" do - it "should create winrm endpoint address" do - expect(subject.send(:endpoint)).to eq("http://localhost:5985/wsman") + context 'when transport is :ssl' do + let(:config) { + VagrantPlugins::CommunicatorWinRM::Config.new.tap do |c| + c.transport = :ssl + c.finalize! + end + } + it "should create winrm endpoint address using https" do + expect(subject.send(:endpoint)).to eq("https://localhost:5986/wsman") + end + end + + context "when transport is :plaintext" do + it "should create winrm endpoint address using http" do + expect(subject.send(:endpoint)).to eq("http://localhost:5985/wsman") + end end end @@ -51,7 +80,7 @@ describe VagrantPlugins::CommunicatorWinRM::WinRMShell do it "should create endpoint options" do expect(subject.send(:endpoint_options)).to eq( { user: "username", pass: "password", host: "localhost", port: 5985, - operation_timeout: 60, basic_auth_only: true }) + basic_auth_only: true, no_ssl_peer_verification: false }) end end diff --git a/test/unit/plugins/guests/linux/cap/mount_shared_folder_test.rb b/test/unit/plugins/guests/linux/cap/mount_shared_folder_test.rb new file mode 100644 index 000000000..047d05e82 --- /dev/null +++ b/test/unit/plugins/guests/linux/cap/mount_shared_folder_test.rb @@ -0,0 +1,44 @@ +require File.expand_path("../../../../../base", __FILE__) + +describe "VagrantPlugins::GuestLinux::Cap::MountSharedFolder" do + let(:machine) { double("machine") } + let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) } + let(:guest) { double("guest") } + + before do + allow(machine).to receive(:guest).and_return(guest) + allow(machine).to receive(:communicate).and_return(communicator) + allow(guest).to receive(:capability).and_return(nil) + end + + describe "smb" do + let(:described_class) do + VagrantPlugins::GuestLinux::Plugin.components.guest_capabilities[:linux].get(:mount_smb_shared_folder) + end + + describe ".mount_shared_folder" do + describe "with a domain" do + let(:mount_command) { "mount -t cifs -o uid=`id -u `,gid=`getent group | cut -d: -f3`,sec=ntlm,username=user,password=pass,domain=domain //host/name " } + before do + communicator.expect_command mount_command + communicator.stub_command mount_command, exit_code: 0 + end + after { communicator.verify_expectations! } + it "should call mount with correct args" do + described_class.mount_smb_shared_folder(machine, 'name', 'guestpath', {:smb_username => "user@domain", :smb_password => "pass", :smb_host => "host"}) + end + end + describe "without a domain" do + let(:mount_command) { "mount -t cifs -o uid=`id -u `,gid=`getent group | cut -d: -f3`,sec=ntlm,username=user,password=pass //host/name " } + before do + communicator.expect_command mount_command + communicator.stub_command mount_command, exit_code: 0 + end + after { communicator.verify_expectations! } + it "should call mount with correct args" do + described_class.mount_smb_shared_folder(machine, 'name', 'guestpath', {:smb_username => "user", :smb_password => "pass", :smb_host => "host"}) + end + end + end + end +end diff --git a/test/unit/plugins/guests/photon/cap/change_host_name_test.rb b/test/unit/plugins/guests/photon/cap/change_host_name_test.rb new file mode 100644 index 000000000..5e9e9e9e1 --- /dev/null +++ b/test/unit/plugins/guests/photon/cap/change_host_name_test.rb @@ -0,0 +1,34 @@ +# encoding: UTF-8 +# Copyright (c) 2015 VMware, Inc. All Rights Reserved. + +require File.expand_path("../../../../../base", __FILE__) + +describe "VagrantPlugins::GuestPhoton::Cap::ChangeHostName" do + let(:described_class) do + VagrantPlugins::GuestPhoton::Plugin.components.guest_capabilities[:photon].get(:change_host_name) + end + let(:machine) { double("machine") } + let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) } + + before do + allow(machine).to receive(:communicate).and_return(communicator) + end + + after do + communicator.verify_expectations! + end + + it 'should change hostname when hostname is differ from current' do + hostname = 'vagrant-photon' + expect(communicator).to receive(:test).with("sudo hostname --fqdn | grep 'vagrant-photon'") + communicator.should_receive(:sudo).with("hostname #{hostname.split('.')[0]}") + described_class.change_host_name(machine, hostname) + end + + it 'should not change hostname when hostname equals current' do + hostname = 'vagrant-photon' + communicator.stub(:test).and_return(true) + communicator.should_not_receive(:sudo) + described_class.change_host_name(machine, hostname) + end +end diff --git a/test/unit/plugins/guests/photon/cap/configure_networks_test.rb b/test/unit/plugins/guests/photon/cap/configure_networks_test.rb new file mode 100644 index 000000000..dc6e9aaf1 --- /dev/null +++ b/test/unit/plugins/guests/photon/cap/configure_networks_test.rb @@ -0,0 +1,40 @@ +# encoding: UTF-8 +# Copyright (c) 2015 VMware, Inc. All Rights Reserved. + +require File.expand_path("../../../../../base", __FILE__) + +describe "VagrantPlugins::GuestPhoton::Cap::ConfigureNetworks" do + let(:described_class) do + VagrantPlugins::GuestPhoton::Plugin.components.guest_capabilities[:photon].get(:configure_networks) + end + let(:machine) { double("machine") } + let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) } + + before do + allow(machine).to receive(:communicate).and_return(communicator) + end + + after do + communicator.verify_expectations! + end + + it 'should configure networks' do + networks = [ + { :type => :static, :ip => '192.168.10.10', :netmask => '255.255.255.0', :interface => 1, :name => 'eth0' }, + { :type => :dhcp, :interface => 2, :name => 'eth1' }, + { :type => :static, :ip => '10.168.10.10', :netmask => '255.255.0.0', :interface => 3, :name => 'docker0' } + ] + communicator.should_receive(:sudo).with("ifconfig | grep 'eth' | cut -f1 -d' '") + communicator.should_receive(:sudo).with('ifconfig 192.168.10.10 netmask 255.255.255.0') + communicator.should_receive(:sudo).with('ifconfig netmask ') + communicator.should_receive(:sudo).with('ifconfig 10.168.10.10 netmask 255.255.0.0') + + allow_message_expectations_on_nil + machine.should_receive(:env).at_least(5).times + machine.env.should_receive(:active_machines).at_least(:twice) + machine.env.active_machines.should_receive(:first) + machine.env.should_receive(:machine) + + described_class.configure_networks(machine, networks) + end +end diff --git a/test/unit/plugins/guests/photon/cap/docker_test.rb b/test/unit/plugins/guests/photon/cap/docker_test.rb new file mode 100644 index 000000000..6a1c726ff --- /dev/null +++ b/test/unit/plugins/guests/photon/cap/docker_test.rb @@ -0,0 +1,26 @@ +# encoding: UTF-8 +# Copyright (c) 2015 VMware, Inc. All Rights Reserved. + +require File.expand_path("../../../../../base", __FILE__) + +describe "VagrantPlugins::GuestPhoton::Cap::Docker" do + let(:described_class) do + VagrantPlugins::GuestPhoton::Plugin.components.guest_capabilities[:photon].get(:docker_daemon_running) + end + let(:machine) { double("machine") } + let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) } + let(:old_hostname) { 'oldhostname.olddomain.tld' } + + before do + allow(machine).to receive(:communicate).and_return(communicator) + end + + after do + communicator.verify_expectations! + end + + it 'should check docker' do + expect(communicator).to receive(:test).with('test -S /run/docker.sock') + described_class.docker_daemon_running(machine) + end +end diff --git a/test/unit/plugins/guests/tinycore/cap/change_host_name_test.rb b/test/unit/plugins/guests/tinycore/cap/change_host_name_test.rb new file mode 100644 index 000000000..ba258261f --- /dev/null +++ b/test/unit/plugins/guests/tinycore/cap/change_host_name_test.rb @@ -0,0 +1,26 @@ +require File.expand_path("../../../../../base", __FILE__) + +describe "VagrantPlugins::GuestTinyCore::Cap::ChangeHostName" do + let(:described_class) do + VagrantPlugins::GuestTinyCore::Plugin.components.guest_capabilities[:tinycore].get(:change_host_name) + end + let(:machine) { double("machine") } + let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) } + let(:old_hostname) { 'boot2docker' } + + before do + allow(machine).to receive(:communicate).and_return(communicator) + communicator.stub_command('hostname -f', stdout: old_hostname) + end + + after do + communicator.verify_expectations! + end + + describe ".change_host_name" do + it "refreshes the hostname service with the sethostname command" do + communicator.expect_command(%q(/usr/bin/sethostname newhostname.newdomain.tld)) + described_class.change_host_name(machine, 'newhostname.newdomain.tld') + end + end +end diff --git a/test/unit/plugins/guests/windows/cap/mount_shared_folder_test.rb b/test/unit/plugins/guests/windows/cap/mount_shared_folder_test.rb index 93fe41799..bd443142b 100644 --- a/test/unit/plugins/guests/windows/cap/mount_shared_folder_test.rb +++ b/test/unit/plugins/guests/windows/cap/mount_shared_folder_test.rb @@ -79,4 +79,23 @@ describe "VagrantPlugins::GuestWindows::Cap::MountSharedFolder" do end end + describe "smb" do + + let(:described_class) do + VagrantPlugins::GuestWindows::Plugin.components.guest_capabilities[:windows].get(:mount_smb_shared_folder) + end + + describe ".mount_shared_folder" do + it "should call mount_volume script with correct args" do + expect(Vagrant::Util::TemplateRenderer).to receive(:render).with( + /.+scripts\/mount_volume.ps1/, options: { + mount_point: "guestpath", + share_name: "name", + vm_provider_unc_path: "\\\\host\\name", + }) + described_class.mount_smb_shared_folder(machine, 'name', 'guestpath', {:smb_username => "user", :smb_password => "pass", :smb_host => "host"}) + end + end + end + end diff --git a/test/unit/plugins/guests/windows/cap/rsync_test.rb b/test/unit/plugins/guests/windows/cap/rsync_test.rb new file mode 100644 index 000000000..e28a5b3fb --- /dev/null +++ b/test/unit/plugins/guests/windows/cap/rsync_test.rb @@ -0,0 +1,26 @@ +require File.expand_path("../../../../../base", __FILE__) + +require Vagrant.source_root.join("plugins/guests/windows/cap/rsync") + +describe "VagrantPlugins::GuestWindows::Cap::RSync" do + let(:described_class) do + VagrantPlugins::GuestWindows::Plugin.components.guest_capabilities[:windows].get(:rsync_pre) + end + let(:machine) { double("machine") } + let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) } + + before do + allow(machine).to receive(:communicate).and_return(communicator) + end + + after do + communicator.verify_expectations! + end + + describe ".rsync_pre" do + it 'makes the guestpath directory with mkdir' do + communicator.expect_command("mkdir '/sync_dir'") + described_class.rsync_pre(machine, guestpath: '/sync_dir') + end + end +end diff --git a/test/unit/plugins/kernel_v2/config/push_test.rb b/test/unit/plugins/kernel_v2/config/push_test.rb new file mode 100644 index 000000000..e1c67996f --- /dev/null +++ b/test/unit/plugins/kernel_v2/config/push_test.rb @@ -0,0 +1,352 @@ +require File.expand_path("../../../../base", __FILE__) + +require Vagrant.source_root.join("plugins/kernel_v2/config/push") + +describe VagrantPlugins::Kernel_V2::PushConfig do + include_context "unit" + + subject { described_class.new } + + describe "#define" do + let(:pushes) { subject.instance_variable_get(:@__defined_pushes) } + + it "pushes the strategy and block onto the defined pushes array" do + subject.define("foo") { "bar" } + subject.define("foo") { "zip" } + subject.define("foo") { "zap" } + + expect(pushes.size).to eq(1) + expect(pushes[:foo].size).to eq(3) + expect(pushes[:foo][0]).to be_a(Array) + expect(pushes[:foo][0][0]).to eq(:foo) + expect(pushes[:foo][0][1]).to be_a(Proc) + end + + context "when no strategy is given" do + it "defaults to the name" do + subject.define("foo") { "bar" } + + expect(pushes.size).to eq(1) + expect(pushes[:foo].size).to eq(1) + expect(pushes[:foo][0]).to be_a(Array) + expect(pushes[:foo][0][0]).to eq(:foo) + expect(pushes[:foo][0][1]).to be_a(Proc) + end + end + + context "when a strategy is given" do + it "uses the strategy" do + subject.define("foo", strategy: "bacon") { "bar" } + + expect(pushes.size).to eq(1) + expect(pushes[:foo].size).to eq(1) + expect(pushes[:foo][0]).to be_a(Array) + expect(pushes[:foo][0][0]).to eq(:bacon) + expect(pushes[:foo][0][1]).to be_a(Proc) + end + end + end + + describe "#merge" do + it "appends defined pushes" do + a = described_class.new.tap do |i| + i.define("foo") { "bar" } + i.define("bar") { "bar" } + end + b = described_class.new.tap do |i| + i.define("foo") { "zip" } + end + + result = a.merge(b) + pushes = result.instance_variable_get(:@__defined_pushes) + + expect(pushes[:foo]).to be_a(Array) + expect(pushes[:foo].size).to eq(2) + + expect(pushes[:bar]).to be_a(Array) + expect(pushes[:bar].size).to eq(1) + end + end + + describe "#__compiled_pushes" do + it "raises an exception if not finalized" do + subject.instance_variable_set(:@__finalized, false) + expect { subject.__compiled_pushes }.to raise_error + end + + it "returns a copy of the compiled pushes" do + pushes = { foo: "bar" } + subject.instance_variable_set(:@__finalized, true) + subject.instance_variable_set(:@__compiled_pushes, pushes) + + expect(subject.__compiled_pushes).to_not be(pushes) + expect(subject.__compiled_pushes).to eq(pushes) + end + end + + describe "#finalize!" do + let(:pushes) { a.merge(b).tap { |r| r.finalize! }.__compiled_pushes } + let(:key) { pushes[:foo][0] } + let(:config) { pushes[:foo][1] } + let(:unset) { Vagrant.plugin("2", :config).const_get(:UNSET_VALUE) } + let(:dummy_klass) { Vagrant::Config::V2::DummyConfig } + + before do + register_plugin("2") do |plugin| + plugin.name "foo" + + plugin.push(:foo) do + Class.new(Vagrant.plugin("2", :push)) + end + + plugin.config(:foo, :push) do + Class.new(Vagrant.plugin("2", :config)) do + attr_accessor :bar + attr_accessor :zip + + def initialize + @bar = self.class.const_get(:UNSET_VALUE) + @zip = self.class.const_get(:UNSET_VALUE) + end + end + end + end + end + + it "compiles the proper configuration with a single strategy" do + instance = described_class.new.tap do |i| + i.define "foo" + end + + instance.finalize! + + pushes = instance.__compiled_pushes + strategy, config = pushes[:foo] + expect(strategy).to eq(:foo) + expect(config.bar).to be(unset) + end + + it "compiles the proper configuration with a single strategy and block" do + instance = described_class.new.tap do |i| + i.define "foo" do |b| + b.bar = 42 + end + end + + instance.finalize! + + pushes = instance.__compiled_pushes + strategy, config = pushes[:foo] + expect(strategy).to eq(:foo) + expect(config.bar).to eq(42) + end + + it "compiles the proper config with a name and explicit strategy" do + instance = described_class.new.tap do |i| + i.define "bar", strategy: "foo" + end + + instance.finalize! + + pushes = instance.__compiled_pushes + strategy, config = pushes[:bar] + expect(strategy).to eq(:foo) + expect(config.bar).to be(unset) + end + + it "compiles the proper config with a name and explicit strategy with block" do + instance = described_class.new.tap do |i| + i.define "bar", strategy: "foo" do |b| + b.bar = 42 + end + end + + instance.finalize! + + pushes = instance.__compiled_pushes + strategy, config = pushes[:bar] + expect(strategy).to eq(:foo) + expect(config.bar).to eq(42) + end + + context "with the same name but different strategy" do + context "with no block" do + let(:a) do + described_class.new.tap do |i| + i.define("foo", strategy: "bar") + end + end + + let(:b) do + described_class.new.tap do |i| + i.define("foo", strategy: "zip") + end + end + + it "chooses the last config" do + expect(key).to eq(:zip) + expect(config).to be_kind_of(dummy_klass) + end + end + + context "with a block" do + let(:a) do + described_class.new.tap do |i| + i.define("foo", strategy: "bar") do |p| + p.bar = "a" + end + end + end + + let(:b) do + described_class.new.tap do |i| + i.define("foo", strategy: "zip") do |p| + p.zip = "b" + end + end + end + + it "chooses the last config" do + expect(key).to eq(:zip) + expect(config).to be_kind_of(dummy_klass) + end + end + + context "with a block, then no block" do + let(:a) do + described_class.new.tap do |i| + i.define("foo", strategy: "bar") do |p| + p.bar, p.zip = "a", "a" + end + end + end + + let(:b) do + described_class.new.tap do |i| + i.define("foo", strategy: "zip") + end + end + + it "chooses the last config" do + expect(key).to eq(:zip) + expect(config).to be_kind_of(dummy_klass) + end + end + + context "with no block, then a block" do + let(:a) do + described_class.new.tap do |i| + i.define("foo", strategy: "bar") + end + end + + let(:b) do + described_class.new.tap do |i| + i.define("foo", strategy: "zip") do |p| + p.bar, p.zip = "b", "b" + end + end + end + + it "chooses the last config" do + expect(key).to eq(:zip) + expect(config).to be_kind_of(dummy_klass) + end + end + end + + context "with the same name twice" do + context "with no block" do + let(:a) do + described_class.new.tap do |i| + i.define("foo") + end + end + + let(:b) do + described_class.new.tap do |i| + i.define("foo") + end + end + + it "merges the configs" do + expect(key).to eq(:foo) + expect(config.bar).to be(unset) + expect(config.zip).to be(unset) + end + end + + context "with a block" do + let(:a) do + described_class.new.tap do |i| + i.define("foo") do |p| + p.bar = "a" + end + end + end + + let(:b) do + described_class.new.tap do |i| + i.define("foo") do |p| + p.zip = "b" + end + end + end + + it "merges the configs" do + expect(key).to eq(:foo) + expect(config.bar).to eq("a") + expect(config.zip).to eq("b") + end + end + + context "with a block, then no block" do + let(:a) do + described_class.new.tap do |i| + i.define("foo") do |p| + p.bar = "a" + end + end + end + + let(:b) do + described_class.new.tap do |i| + i.define("foo") + end + end + + it "merges the configs" do + expect(key).to eq(:foo) + expect(config.bar).to eq("a") + expect(config.zip).to be(unset) + end + end + + context "with no block, then a block" do + let(:a) do + described_class.new.tap do |i| + i.define("foo", strategy: "bar") + end + end + + let(:b) do + described_class.new.tap do |i| + i.define("foo", strategy: "zip") do |p| + p.zip = "b" + end + end + end + + it "merges the configs" do + expect(key).to eq(:zip) + expect(config).to be_kind_of(dummy_klass) + end + end + end + + it "sets @__finalized to true" do + subject.finalize! + expect(subject.instance_variable_get(:@__finalized)).to be(true) + end + end +end diff --git a/test/unit/plugins/kernel_v2/config/ssh_test.rb b/test/unit/plugins/kernel_v2/config/ssh_test.rb index 61365518b..15e23a7a0 100644 --- a/test/unit/plugins/kernel_v2/config/ssh_test.rb +++ b/test/unit/plugins/kernel_v2/config/ssh_test.rb @@ -11,4 +11,11 @@ describe VagrantPlugins::Kernel_V2::SSHConfig do expect(subject.default.username).to eq("vagrant") end end + + describe "#sudo_command" do + it "defaults properly" do + subject.finalize! + expect(subject.sudo_command).to eq("sudo -E -H %c") + end + end end diff --git a/test/unit/plugins/kernel_v2/config/vm_test.rb b/test/unit/plugins/kernel_v2/config/vm_test.rb index bbe2a5979..3d9b01213 100644 --- a/test/unit/plugins/kernel_v2/config/vm_test.rb +++ b/test/unit/plugins/kernel_v2/config/vm_test.rb @@ -23,6 +23,13 @@ describe VagrantPlugins::Kernel_V2::VMConfig do end end + def find_network(name) + network_definitions = subject.networks.map do |n| + n[1] + end + network_definitions.find {|n| n[:id] == name} + end + before do env = double("env") env.stub(root_path: nil) @@ -121,28 +128,6 @@ describe VagrantPlugins::Kernel_V2::VMConfig do end end - describe "#define" do - it "should allow regular names" do - subject.define "foo" - subject.finalize! - - assert_valid - end - - [ - "foo [1]", - "bar {2}", - "foo/bar", - ].each do |name| - it "should disallow names with brackets" do - subject.define name - subject.finalize! - - assert_invalid - end - end - end - describe "#guest" do it "is nil by default" do subject.finalize! @@ -183,6 +168,7 @@ describe VagrantPlugins::Kernel_V2::VMConfig do subject.finalize! n = subject.networks expect(n.length).to eq(2) + expect(n[0][0]).to eq(:forwarded_port) expect(n[0][1][:guest]).to eq(5985) expect(n[0][1][:host]).to eq(55985) @@ -190,9 +176,10 @@ describe VagrantPlugins::Kernel_V2::VMConfig do expect(n[0][1][:id]).to eq("winrm") expect(n[1][0]).to eq(:forwarded_port) - expect(n[1][1][:guest]).to eq(22) - expect(n[1][1][:host]).to eq(2222) - expect(n[1][1][:id]).to eq("ssh") + expect(n[1][1][:guest]).to eq(5986) + expect(n[1][1][:host]).to eq(55986) + expect(n[1][1][:host_ip]).to eq("127.0.0.1") + expect(n[1][1][:id]).to eq("winrm-ssl") end it "allows overriding SSH" do @@ -214,12 +201,22 @@ describe VagrantPlugins::Kernel_V2::VMConfig do guest: 22, host: 14100, id: "winrm" subject.finalize! - n = subject.networks - expect(n.length).to eq(2) - expect(n[0][0]).to eq(:forwarded_port) - expect(n[0][1][:guest]).to eq(22) - expect(n[0][1][:host]).to eq(14100) - expect(n[0][1][:id]).to eq("winrm") + winrm_network = find_network 'winrm' + expect(winrm_network[:guest]).to eq(22) + expect(winrm_network[:host]).to eq(14100) + expect(winrm_network[:id]).to eq("winrm") + end + + it "allows overriding WinRM SSL" do + subject.communicator = :winrmssl + subject.network "forwarded_port", + guest: 22, host: 14100, id: "winrmssl" + subject.finalize! + + winrmssl_network = find_network 'winrmssl' + expect(winrmssl_network[:guest]).to eq(22) + expect(winrmssl_network[:host]).to eq(14100) + expect(winrmssl_network[:id]).to eq("winrmssl") end it "turns all forwarded port ports to ints" do @@ -262,6 +259,41 @@ describe VagrantPlugins::Kernel_V2::VMConfig do end end + describe "#provider and #__providers" do + it "returns the providers in order" do + subject.provider "foo" + subject.provider "bar" + subject.finalize! + + expect(subject.__providers).to eq([:foo, :bar]) + end + + describe "merging" do + it "prioritizes new orders in later configs" do + subject.provider "foo" + + other = described_class.new + other.provider "bar" + + merged = subject.merge(other) + + expect(merged.__providers).to eq([:foo, :bar]) + end + + it "prioritizes duplicates in new orders in later configs" do + subject.provider "foo" + + other = described_class.new + other.provider "bar" + other.provider "foo" + + merged = subject.merge(other) + + expect(merged.__providers).to eq([:foo, :bar]) + end + end + end + describe "#provider and #get_provider_config" do it "compiles the configurations for a provider" do subject.provider "virtualbox" do |vb| @@ -306,8 +338,8 @@ describe VagrantPlugins::Kernel_V2::VMConfig do end it "allows provisioner settings to be overriden" do - subject.provision("shell", path: "foo", id: "s") { |s| s.inline = "foo" } - subject.provision("shell", inline: "bar", id: "s") { |s| s.args = "bar" } + subject.provision("s", path: "foo", type: "shell") { |s| s.inline = "foo" } + subject.provision("s", inline: "bar", type: "shell") { |s| s.args = "bar" } subject.finalize! r = subject.provisioners @@ -377,12 +409,12 @@ describe VagrantPlugins::Kernel_V2::VMConfig do end it "uses the proper order when merging overrides" do - subject.provision("shell", inline: "foo", id: "original") - subject.provision("shell", inline: "other", id: "other") + subject.provision("original", inline: "foo", type: "shell") + subject.provision("other", inline: "other", type: "shell") other = described_class.new other.provision("shell", inline: "bar") - other.provision("shell", inline: "foo-overload", id: "original") + other.provision("original", inline: "foo-overload", type: "shell") merged = subject.merge(other) merged_provs = merged.provisioners @@ -397,13 +429,13 @@ describe VagrantPlugins::Kernel_V2::VMConfig do end it "can preserve order for overrides" do - subject.provision("shell", inline: "foo", id: "original") - subject.provision("shell", inline: "other", id: "other") + subject.provision("original", inline: "foo", type: "shell") + subject.provision("other", inline: "other", type: "shell") other = described_class.new other.provision("shell", inline: "bar") other.provision( - "shell", inline: "foo-overload", id: "original", + "original", inline: "foo-overload", type: "shell", preserve_order: true) merged = subject.merge(other) @@ -448,6 +480,12 @@ describe VagrantPlugins::Kernel_V2::VMConfig do expect(sf["/vagrant"][:disabled]).to be_false expect(sf["/vagrant"][:foo]).to eq(:bar) end + + it "is not an error if guest path is empty" do + subject.synced_folder(".", "") + subject.finalize! + assert_valid + end end describe "#usable_port_range" do diff --git a/test/unit/plugins/providers/docker/config_test.rb b/test/unit/plugins/providers/docker/config_test.rb index ac20f6bc3..7579092f9 100644 --- a/test/unit/plugins/providers/docker/config_test.rb +++ b/test/unit/plugins/providers/docker/config_test.rb @@ -47,8 +47,14 @@ describe VagrantPlugins::DockerProvider::Config do its(:image) { should be_nil } its(:name) { should be_nil } its(:privileged) { should be_false } + its(:stop_timeout) { should eq(1) } its(:vagrant_machine) { should be_nil } its(:vagrant_vagrantfile) { should be_nil } + + its(:auth_server) { should be_nil } + its(:email) { should eq("") } + its(:username) { should eq("") } + its(:password) { should eq("") } end before do @@ -76,9 +82,15 @@ describe VagrantPlugins::DockerProvider::Config do subject.finalize! assert_valid end + end - it "should be invalid with a directory that doesn't have a Dockerfile" do - subject.build_dir = temporary_dir.to_s + describe "#create_args" do + before do + valid_defaults + end + + it "is invalid if it isn't an array" do + subject.create_args = "foo" subject.finalize! assert_invalid end diff --git a/test/unit/plugins/providers/docker/driver_test.rb b/test/unit/plugins/providers/docker/driver_test.rb index 337436a51..dbfde3595 100644 --- a/test/unit/plugins/providers/docker/driver_test.rb +++ b/test/unit/plugins/providers/docker/driver_test.rb @@ -17,7 +17,7 @@ describe VagrantPlugins::DockerProvider::Driver do ports: '8080:80', volumes: '/host/path:guest/path', detach: true, - links: {janis: 'joplin'}, + links: [[:janis, 'joplin'], [:janis, 'janis']], env: {key: 'value'}, name: cid, hostname: 'jimi-hendrix', @@ -43,7 +43,9 @@ describe VagrantPlugins::DockerProvider::Driver do end it 'links containers' do - expect(cmd_executed).to match(/--link #{params[:links].to_a.flatten.join(':')} .+ #{Regexp.escape params[:image]}/) + params[:links].each do |link| + expect(cmd_executed).to match(/--link #{link.join(':')} .+ #{Regexp.escape params[:image]}/) + end end it 'sets environmental variables' do @@ -82,6 +84,13 @@ describe VagrantPlugins::DockerProvider::Driver do end end + describe '#pull' do + it 'should pull images' do + subject.should_receive(:execute).with('docker', 'pull', 'foo') + subject.pull('foo') + end + end + describe '#running?' do let(:result) { subject.running?(cid) } @@ -140,7 +149,12 @@ describe VagrantPlugins::DockerProvider::Driver do it 'stops the container' do subject.should_receive(:execute).with('docker', 'stop', '-t', '1', cid) - subject.stop(cid) + subject.stop(cid, 1) + end + + it "stops the container with the set timeout" do + subject.should_receive(:execute).with('docker', 'stop', '-t', '5', cid) + subject.stop(cid, 5) end end @@ -149,7 +163,7 @@ describe VagrantPlugins::DockerProvider::Driver do it 'does not stop container' do subject.should_not_receive(:execute).with('docker', 'stop', '-t', '1', cid) - subject.stop(cid) + subject.stop(cid, 1) end end end diff --git a/test/unit/plugins/providers/hyperv/config_test.rb b/test/unit/plugins/providers/hyperv/config_test.rb index 5177dca59..e7b5a6d71 100644 --- a/test/unit/plugins/providers/hyperv/config_test.rb +++ b/test/unit/plugins/providers/hyperv/config_test.rb @@ -9,10 +9,49 @@ describe VagrantPlugins::HyperV::Config do subject.finalize! expect(subject.ip_address_timeout).to eq(180) end - it "defaults to a number" do subject.finalize! expect(subject.ip_address_timeout).to eq(120) end end + + describe "#vlan_id" do + it "can be set" do + subject.vlan_id = 100 + subject.finalize! + expect(subject.vlan_id).to eq(100) + end + end + + describe "#vmname" do + it "can be set" do + subject.vmname = "test" + subject.finalize! + expect(subject.vmname).to eq("test") + end + end + + describe "#memory" do + it "can be set" do + subject.memory = 512 + subject.finalize! + expect(subject.memory).to eq(512) + end + end + + describe "#maxmemory" do + it "can be set" do + subject.maxmemory = 1024 + subject.finalize! + expect(subject.maxmemory).to eq(1024) + end + end + + describe "#cpus" do + it "can be set" do + subject.cpus = 2 + subject.finalize! + expect(subject.cpus).to eq(2) + end + end end diff --git a/test/unit/plugins/providers/virtualbox/action/network_test.rb b/test/unit/plugins/providers/virtualbox/action/network_test.rb new file mode 100644 index 000000000..e9986492d --- /dev/null +++ b/test/unit/plugins/providers/virtualbox/action/network_test.rb @@ -0,0 +1,142 @@ +require_relative "../base" + +require "vagrant/util/platform" + +describe VagrantPlugins::ProviderVirtualBox::Action::Network do + include_context "unit" + include_context "virtualbox" + + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env + end + + let(:machine) do + iso_env.machine(iso_env.machine_names[0], :virtualbox).tap do |m| + m.provider.stub(driver: driver) + end + end + + let(:env) {{ machine: machine, ui: machine.ui }} + let(:app) { lambda { |*args| }} + let(:driver) { double("driver") } + + let(:nics) { {} } + + subject { described_class.new(app, env) } + + before do + allow(driver).to receive(:enable_adapters) + allow(driver).to receive(:read_network_interfaces) { nics } + end + + it "calls the next action in the chain" do + called = false + app = lambda { |*args| called = true } + + action = described_class.new(app, env) + action.call(env) + + expect(called).to eq(true) + end + + context "with a dhcp private network" do + let(:bridgedifs) { [] } + let(:hostonlyifs) { [] } + let(:dhcpservers) { [] } + let(:guest) { double("guest") } + let(:network_args) {{ type: :dhcp }} + + before do + machine.config.vm.network 'private_network', network_args + allow(driver).to receive(:read_bridged_interfaces) { bridgedifs } + allow(driver).to receive(:read_host_only_interfaces) { hostonlyifs } + allow(driver).to receive(:read_dhcp_servers) { dhcpservers } + allow(machine).to receive(:guest) { guest } + end + + it "creates a host only interface and a dhcp server using default ips, then tells the guest to configure the network after boot" do + allow(driver).to receive(:create_host_only_network) {{ name: 'vboxnet0' }} + allow(driver).to receive(:create_dhcp_server) + allow(guest).to receive(:capability) + + subject.call(env) + + expect(driver).to have_received(:create_host_only_network).with({ + adapter_ip: '172.28.128.1', + netmask: '255.255.255.0', + }) + + expect(driver).to have_received(:create_dhcp_server).with('vboxnet0', { + adapter_ip: "172.28.128.1", + auto_config: true, + ip: "172.28.128.1", + mac: nil, + name: nil, + netmask: "255.255.255.0", + nic_type: nil, + type: :dhcp, + dhcp_ip: "172.28.128.2", + dhcp_lower: "172.28.128.3", + dhcp_upper: "172.28.128.254", + adapter: 2 + }) + + expect(guest).to have_received(:capability).with(:configure_networks, [{ + type: :dhcp, + adapter_ip: "172.28.128.1", + ip: "172.28.128.1", + netmask: "255.255.255.0", + auto_config: true, + interface: nil + }]) + end + + context "when the default vbox dhcpserver is present from a fresh vbox install (see issue #3803)" do + let(:dhcpservers) {[ + { + network_name: 'HostInterfaceNetworking-vboxnet0', + network: 'vboxnet0', + ip: '192.168.56.100', + netmask: '255.255.255.0', + lower: '192.168.56.101', + upper: '192.168.56.254' + } + ]} + + it "removes the invalid dhcpserver so it won't collide with any host only interface" do + allow(driver).to receive(:remove_dhcp_server) + allow(driver).to receive(:create_host_only_network) {{ name: 'vboxnet0' }} + allow(driver).to receive(:create_dhcp_server) + allow(guest).to receive(:capability) + + subject.call(env) + + expect(driver).to have_received(:remove_dhcp_server).with('HostInterfaceNetworking-vboxnet0') + end + + context "but the user has intentionally configured their network just that way" do + let (:network_args) {{ + type: :dhcp, + adapter_ip: '192.168.56.1', + dhcp_ip: '192.168.56.100', + dhcp_lower: '192.168.56.101', + dhcp_upper: '192.168.56.254' + }} + + it "does not attempt to remove the dhcpserver" do + allow(driver).to receive(:remove_dhcp_server) + allow(driver).to receive(:create_host_only_network) {{ name: 'vboxnet0' }} + allow(driver).to receive(:create_dhcp_server) + allow(guest).to receive(:capability) + + subject.call(env) + + expect(driver).not_to have_received(:remove_dhcp_server).with('HostInterfaceNetworking-vboxnet0') + end + end + end + end +end diff --git a/test/unit/plugins/providers/virtualbox/action/prepare_nfs_settings_test.rb b/test/unit/plugins/providers/virtualbox/action/prepare_nfs_settings_test.rb index 8acc6fa28..3cead74f4 100644 --- a/test/unit/plugins/providers/virtualbox/action/prepare_nfs_settings_test.rb +++ b/test/unit/plugins/providers/virtualbox/action/prepare_nfs_settings_test.rb @@ -22,10 +22,21 @@ describe VagrantPlugins::ProviderVirtualBox::Action::PrepareNFSSettings do let(:env) {{ machine: machine }} let(:app) { lambda { |*args| }} let(:driver) { double("driver") } + let(:host) { double("host") } subject { described_class.new(app, env) } + before do + env[:test] = true + allow(machine.env).to receive(:host) { host } + allow(host).to receive(:capability).with(:nfs_installed) { true } + end + it "calls the next action in the chain" do + driver.stub(read_network_interfaces: {2 => {type: :hostonly, hostonly: "vmnet2"}}) + driver.stub(read_host_only_interfaces: [{name: "vmnet2", ip: "1.2.3.4"}]) + allow(driver).to receive(:read_guest_ip).with(1).and_return("2.3.4.5") + called = false app = lambda { |*args| called = true } diff --git a/test/unit/plugins/providers/virtualbox/support/shared/virtualbox_driver_version_4_x_examples.rb b/test/unit/plugins/providers/virtualbox/support/shared/virtualbox_driver_version_4_x_examples.rb index cb84e2325..88f199a82 100644 --- a/test/unit/plugins/providers/virtualbox/support/shared/virtualbox_driver_version_4_x_examples.rb +++ b/test/unit/plugins/providers/virtualbox/support/shared/virtualbox_driver_version_4_x_examples.rb @@ -3,6 +3,76 @@ shared_examples "a version 4.x virtualbox driver" do |options| raise ArgumentError, "Need virtualbox context to use these shared examples." if !(defined? vbox_context) end + describe "read_dhcp_servers" do + before { + expect(subprocess).to receive(:execute). + with("VBoxManage", "list", "dhcpservers", an_instance_of(Hash)). + and_return(subprocess_result(stdout: output)) + } + + context "with empty output" do + let(:output) { "" } + + it "returns an empty list" do + expect(subject.read_dhcp_servers).to eq([]) + end + end + + context "with a single dhcp server" do + let(:output) { + <<-OUTPUT.gsub(/^ */, '') + NetworkName: HostInterfaceNetworking-vboxnet0 + IP: 172.28.128.2 + NetworkMask: 255.255.255.0 + lowerIPAddress: 172.28.128.3 + upperIPAddress: 172.28.128.254 + Enabled: Yes + + OUTPUT + } + + + it "returns a list with one entry describing that server" do + expect(subject.read_dhcp_servers).to eq([{ + network_name: 'HostInterfaceNetworking-vboxnet0', + network: 'vboxnet0', + ip: '172.28.128.2', + netmask: '255.255.255.0', + lower: '172.28.128.3', + upper: '172.28.128.254', + }]) + end + end + + context "with a multiple dhcp servers" do + let(:output) { + <<-OUTPUT.gsub(/^ */, '') + NetworkName: HostInterfaceNetworking-vboxnet0 + IP: 172.28.128.2 + NetworkMask: 255.255.255.0 + lowerIPAddress: 172.28.128.3 + upperIPAddress: 172.28.128.254 + Enabled: Yes + + NetworkName: HostInterfaceNetworking-vboxnet1 + IP: 10.0.0.2 + NetworkMask: 255.255.255.0 + lowerIPAddress: 10.0.0.3 + upperIPAddress: 10.0.0.254 + Enabled: Yes + OUTPUT + } + + + it "returns a list with one entry for each server" do + expect(subject.read_dhcp_servers).to eq([ + {network_name: 'HostInterfaceNetworking-vboxnet0', network: 'vboxnet0', ip: '172.28.128.2', netmask: '255.255.255.0', lower: '172.28.128.3', upper: '172.28.128.254'}, + {network_name: 'HostInterfaceNetworking-vboxnet1', network: 'vboxnet1', ip: '10.0.0.2', netmask: '255.255.255.0', lower: '10.0.0.3', upper: '10.0.0.254'}, + ]) + end + end + end + describe "read_guest_property" do it "reads the guest property of the machine referenced by the UUID" do key = "/Foo/Bar" @@ -38,5 +108,108 @@ shared_examples "a version 4.x virtualbox driver" do |options| expect(value).to eq("127.1.2.3") end + + it "does not accept 0.0.0.0 as a valid IP address" do + key = "/VirtualBox/GuestInfo/Net/1/V4/IP" + + expect(subprocess).to receive(:execute). + with("VBoxManage", "guestproperty", "get", uuid, key, an_instance_of(Hash)). + and_return(subprocess_result(stdout: "Value: 0.0.0.0")) + + expect { subject.read_guest_ip(1) }. + to raise_error Vagrant::Errors::VirtualBoxGuestPropertyNotFound + end + end + + describe "read_host_only_interfaces" do + before { + expect(subprocess).to receive(:execute). + with("VBoxManage", "list", "hostonlyifs", an_instance_of(Hash)). + and_return(subprocess_result(stdout: output)) + } + + context "with empty output" do + let(:output) { "" } + + it "returns an empty list" do + expect(subject.read_host_only_interfaces).to eq([]) + end + end + + context "with a single host only interface" do + let(:output) { + <<-OUTPUT.gsub(/^ */, '') + Name: vboxnet0 + GUID: 786f6276-656e-4074-8000-0a0027000000 + DHCP: Disabled + IPAddress: 172.28.128.1 + NetworkMask: 255.255.255.0 + IPV6Address: + IPV6NetworkMaskPrefixLength: 0 + HardwareAddress: 0a:00:27:00:00:00 + MediumType: Ethernet + Status: Up + VBoxNetworkName: HostInterfaceNetworking-vboxnet0 + + OUTPUT + } + + it "returns a list with one entry describing that interface" do + expect(subject.read_host_only_interfaces).to eq([{ + name: 'vboxnet0', + ip: '172.28.128.1', + netmask: '255.255.255.0', + status: 'Up', + }]) + end + end + + context "with multiple host only interfaces" do + let(:output) { + <<-OUTPUT.gsub(/^ */, '') + Name: vboxnet0 + GUID: 786f6276-656e-4074-8000-0a0027000000 + DHCP: Disabled + IPAddress: 172.28.128.1 + NetworkMask: 255.255.255.0 + IPV6Address: + IPV6NetworkMaskPrefixLength: 0 + HardwareAddress: 0a:00:27:00:00:00 + MediumType: Ethernet + Status: Up + VBoxNetworkName: HostInterfaceNetworking-vboxnet0 + + Name: vboxnet1 + GUID: 5764a976-8479-8388-1245-8a0048080840 + DHCP: Disabled + IPAddress: 10.0.0.1 + NetworkMask: 255.255.255.0 + IPV6Address: + IPV6NetworkMaskPrefixLength: 0 + HardwareAddress: 0a:00:27:00:00:01 + MediumType: Ethernet + Status: Up + VBoxNetworkName: HostInterfaceNetworking-vboxnet1 + + OUTPUT + } + + it "returns a list with one entry for each interface" do + expect(subject.read_host_only_interfaces).to eq([ + {name: 'vboxnet0', ip: '172.28.128.1', netmask: '255.255.255.0', status: 'Up'}, + {name: 'vboxnet1', ip: '10.0.0.1', netmask: '255.255.255.0', status: 'Up'}, + ]) + end + end + end + + describe "remove_dhcp_server" do + it "removes the dhcp server with the specified network name" do + expect(subprocess).to receive(:execute). + with("VBoxManage", "dhcpserver", "remove", "--netname", "HostInterfaceNetworking-vboxnet0", an_instance_of(Hash)). + and_return(subprocess_result(stdout: '')) + + subject.remove_dhcp_server("HostInterfaceNetworking-vboxnet0") + end end end diff --git a/test/unit/plugins/provisioners/ansible/provisioner_test.rb b/test/unit/plugins/provisioners/ansible/provisioner_test.rb index 45951e8b3..ac1a80f97 100644 --- a/test/unit/plugins/provisioners/ansible/provisioner_test.rb +++ b/test/unit/plugins/provisioners/ansible/provisioner_test.rb @@ -50,9 +50,15 @@ VF let(:generated_inventory_file) { File.join(generated_inventory_dir, 'vagrant_ansible_inventory') } before do + Vagrant::Util::Platform.stub(solaris?: false) + machine.stub(ssh_info: ssh_info) machine.env.stub(active_machines: [[iso_env.machine_names[0], :dummy], [iso_env.machine_names[1], :dummy]]) + stubbed_ui = Vagrant::UI::Colored.new + stubbed_ui.stub(detail: "") + machine.env.stub(ui: stubbed_ui) + config.playbook = 'playbook.yml' end @@ -60,13 +66,16 @@ VF # Class methods for code reuse across examples # - def self.it_should_set_arguments_and_environment_variables(expected_args_count = 5, expected_vars_count = 3, expected_host_key_checking = false) + def self.it_should_set_arguments_and_environment_variables( + expected_args_count = 6, expected_vars_count = 4, expected_host_key_checking = false, expected_transport_mode = "ssh") + it "sets implicit arguments in a specific order" do expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| expect(args[0]).to eq("ansible-playbook") - expect(args[1]).to eq("--private-key=#{machine.ssh_info[:private_key_path][0]}") - expect(args[2]).to eq("--user=#{machine.ssh_info[:username]}") + expect(args[1]).to eq("--user=#{machine.ssh_info[:username]}") + expect(args[2]).to eq("--connection=ssh") + expect(args[3]).to eq("--timeout=30") inventory_count = args.count { |x| x =~ /^--inventory-file=.+$/ } expect(inventory_count).to be > 0 @@ -77,18 +86,18 @@ VF it "sets --limit argument" do expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| - raw_limits = [] + all_limits = args.select { |x| x =~ /^(--limit=|-l)/ } if config.raw_arguments raw_limits = config.raw_arguments.select { |x| x =~ /^(--limit=|-l)/ } - end - all_limits = args.select { |x| x =~ /^(--limit=|-l)/ } - expect(all_limits.length - raw_limits.length).to eq(1) - - if config.limit - limit = config.limit.kind_of?(Array) ? config.limit.join(',') : config.limit - expect(all_limits.last).to eq("--limit=#{limit}") + expect(all_limits.length - raw_limits.length).to eq(1) + expect(all_limits.last).to eq(raw_limits.last) else - expect(all_limits.first).to eq("--limit=#{machine.name}") + if config.limit + limit = config.limit.kind_of?(Array) ? config.limit.join(',') : config.limit + expect(all_limits.last).to eq("--limit=#{limit}") + else + expect(all_limits.first).to eq("--limit=#{machine.name}") + end end } end @@ -96,7 +105,15 @@ VF it "exports environment variables" do expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| cmd_opts = args.last + + if expected_host_key_checking + expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to_not include("-o UserKnownHostsFile=/dev/null") + else + expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o UserKnownHostsFile=/dev/null") + end + expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentitiesOnly=yes") expect(cmd_opts[:env]['ANSIBLE_FORCE_COLOR']).to eql("true") + expect(cmd_opts[:env]).to_not include("ANSIBLE_NOCOLOR") expect(cmd_opts[:env]['ANSIBLE_HOST_KEY_CHECKING']).to eql(expected_host_key_checking.to_s) expect(cmd_opts[:env]['PYTHONUNBUFFERED']).to eql(1) } @@ -109,6 +126,15 @@ VF expect(args.last[:env].length).to eq(expected_vars_count) } end + + it "enables '#{expected_transport_mode}' transport mode" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + index = args.rindex("--connection=#{expected_transport_mode}") + expect(index).to be > 0 + expect(find_last_argument_after(index, args, /--connection=\w+/)).to be_false + } + end + end def self.it_should_set_optional_arguments(arg_map) @@ -126,35 +152,7 @@ VF end end - def self.it_should_use_smart_transport_mode - it "does not export ANSIBLE_SSH_ARGS" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| - cmd_opts = args.last - expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to be_nil - } - end - - it "does not force any transport mode" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| - total = args.count { |x| x =~ /^--connection=\w+$/ } - expect(total).to eql(0) - } - end - end - - def self.it_should_use_transport_mode(transport_mode) - it "enables '#{transport_mode}' transport mode" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| - index = args.rindex("--connection=#{transport_mode}") - expect(index).to be > 0 - expect(find_last_argument_after(index, args, /--connection=\w+/)).to be_false - } - end - end - - def self.it_should_force_ssh_transport_mode - it_should_use_transport_mode('ssh') - + def self.it_should_explicitly_enable_ansible_ssh_control_persist_defaults it "configures ControlPersist (like Ansible defaults) via ANSIBLE_SSH_ARGS" do expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| cmd_opts = args.last @@ -170,7 +168,7 @@ VF expect(config.inventory_path).to be_nil expect(File.exists?(generated_inventory_file)).to be_true inventory_content = File.read(generated_inventory_file) - expect(inventory_content).to include("#{machine.name} ansible_ssh_host=#{machine.ssh_info[:host]} ansible_ssh_port=#{machine.ssh_info[:port]}\n") + expect(inventory_content).to include("#{machine.name} ansible_ssh_host=#{machine.ssh_info[:host]} ansible_ssh_port=#{machine.ssh_info[:port]} ansible_ssh_private_key_file=#{machine.ssh_info[:private_key_path][0]}\n") expect(inventory_content).to include("# MISSING: '#{iso_env.machine_names[1]}' machine was probably removed without using Vagrant. This machine should be recreated.\n") } end @@ -210,7 +208,6 @@ VF describe "with default options" do it_should_set_arguments_and_environment_variables - it_should_use_smart_transport_mode it_should_create_and_use_generated_inventory it "does not add any group section to the generated inventory" do @@ -279,8 +276,7 @@ VF config.host_key_checking = true end - it_should_set_arguments_and_environment_variables 5, 3, true - it_should_use_smart_transport_mode + it_should_set_arguments_and_environment_variables 6, 4, true end describe "with boolean (flag) options disabled" do @@ -292,7 +288,7 @@ VF config.sudo_user = 'root' end - it_should_set_arguments_and_environment_variables 6 + it_should_set_arguments_and_environment_variables 7 it_should_set_optional_arguments({ "sudo_user" => "--sudo-user=root" }) it "it does not set boolean flag when corresponding option is set to false" do @@ -308,6 +304,7 @@ VF before do config.sudo = false config.skip_tags = %w(foo bar) + config.limit = "all" config.raw_arguments = ["--connection=paramiko", "--skip-tags=ignored", "--module-path=/other/modules", @@ -316,12 +313,11 @@ VF "--limit=foo", "--limit=bar", "--inventory-file=/forget/it/my/friend", + "--user=lion", "--new-arg=yeah"] end - it_should_set_arguments_and_environment_variables 15 - it_should_create_and_use_generated_inventory - it_should_use_transport_mode('paramiko') + it_should_set_arguments_and_environment_variables 17, 4, false, "paramiko" it "sets all raw arguments" do expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| @@ -331,9 +327,12 @@ VF } end - it "sets raw arguments before arguments related to supported options" do + it "sets raw arguments after arguments related to supported options" do expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| - expect(args.index("--skip-tags=foo,bar")).to be > args.index("--skip-tags=ignored") + expect(args.index("--user=lion")).to be > args.index("--user=testuser") + expect(args.index("--inventory-file=/forget/it/my/friend")).to be > args.index("--inventory-file=#{generated_inventory_dir}") + expect(args.index("--limit=bar")).to be > args.index("--limit=all") + expect(args.index("--skip-tags=ignored")).to be > args.index("--skip-tags=foo,bar") } end @@ -359,7 +358,6 @@ VF end it_should_set_arguments_and_environment_variables - it_should_use_smart_transport_mode it "does not generate the inventory and uses given inventory path instead" do expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| @@ -375,7 +373,7 @@ VF config.ask_vault_pass = true end - it_should_set_arguments_and_environment_variables 6 + it_should_set_arguments_and_environment_variables 7 it "should ask the vault password" do expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| @@ -389,7 +387,7 @@ VF config.vault_password_file = existing_file end - it_should_set_arguments_and_environment_variables 6 + it_should_set_arguments_and_environment_variables 7 it "uses the given vault password file" do expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| @@ -404,7 +402,7 @@ VF end it_should_set_arguments_and_environment_variables 6, 4 - it_should_force_ssh_transport_mode + it_should_explicitly_enable_ansible_ssh_control_persist_defaults it "passes custom SSH options via ANSIBLE_SSH_ARGS with the highest priority" do expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| @@ -438,7 +436,7 @@ VF end it_should_set_arguments_and_environment_variables 6, 4 - it_should_force_ssh_transport_mode + it_should_explicitly_enable_ansible_ssh_control_persist_defaults it "passes additional Identity Files via ANSIBLE_SSH_ARGS" do expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| @@ -455,7 +453,7 @@ VF end it_should_set_arguments_and_environment_variables 6, 4 - it_should_force_ssh_transport_mode + it_should_explicitly_enable_ansible_ssh_control_persist_defaults it "enables SSH-Forwarding via ANSIBLE_SSH_ARGS" do expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| @@ -470,12 +468,26 @@ VF config.verbose = 'v' end - it_should_set_arguments_and_environment_variables 6 + it_should_set_arguments_and_environment_variables 7 it_should_set_optional_arguments({ "verbose" => "-v" }) it "shows the ansible-playbook command" do expect(machine.env.ui).to receive(:detail).with { |full_command| - expect(full_command).to eq("ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=false PYTHONUNBUFFERED=1 ansible-playbook --private-key=/path/to/my/key --user=testuser --limit='machine1' --inventory-file=#{generated_inventory_dir} -v playbook.yml") + expect(full_command).to eq("PYTHONUNBUFFERED=1 ANSIBLE_HOST_KEY_CHECKING=false ANSIBLE_FORCE_COLOR=true ANSIBLE_SSH_ARGS='-o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --user=testuser --connection=ssh --timeout=30 --limit='machine1' --inventory-file=#{generated_inventory_dir} -v playbook.yml") + } + end + end + + describe "without colorized output" do + before do + machine.env.stub(ui: Vagrant::UI::Basic.new) + end + + it "disables ansible-playbook colored output" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + cmd_opts = args.last + expect(cmd_opts[:env]).to_not include("ANSIBLE_FORCE_COLOR") + expect(cmd_opts[:env]['ANSIBLE_NOCOLOR']).to eql("true") } end end @@ -501,15 +513,15 @@ VF config.skip_tags = %w(foo bar) config.limit = 'machine*:&vagrant:!that_one' config.start_at_task = 'an awesome task' - config.raw_arguments = ["--why-not", "--su-user=foot", "--ask-su-pass", "--limit=all"] + config.raw_arguments = ["--why-not", "--su-user=foot", "--ask-su-pass", "--limit=all", "--private-key=./myself.key"] # environment variables config.host_key_checking = true config.raw_ssh_args = ['-o ControlMaster=no'] end - it_should_set_arguments_and_environment_variables 20, 4, true - it_should_force_ssh_transport_mode + it_should_set_arguments_and_environment_variables 21, 4, true + it_should_explicitly_enable_ansible_ssh_control_persist_defaults it_should_set_optional_arguments({ "extra_vars" => "--extra-vars=@#{File.expand_path(__FILE__)}", "sudo" => "--sudo", "sudo_user" => "--sudo-user=deployer", @@ -522,21 +534,97 @@ VF "limit" => "--limit=machine*:&vagrant:!that_one", "start_at_task" => "--start-at-task=an awesome task", }) - + it "also includes given raw arguments" do expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + expect(args).to include("--why-not") expect(args).to include("--su-user=foot") expect(args).to include("--ask-su-pass") - expect(args).to include("--why-not") + expect(args).to include("--limit=all") + expect(args).to include("--private-key=./myself.key") } end it "shows the ansible-playbook command, with additional quotes when required" do expect(machine.env.ui).to receive(:detail).with { |full_command| - expect(full_command).to eq("ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=true PYTHONUNBUFFERED=1 ANSIBLE_SSH_ARGS='-o IdentityFile=/my/key2 -o ForwardAgent=yes -o ControlMaster=no -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --private-key=/my/key1 --user=testuser --connection=ssh --why-not --su-user=foot --ask-su-pass --limit='all' --inventory-file=#{generated_inventory_dir} --extra-vars=@#{File.expand_path(__FILE__)} --sudo --sudo-user=deployer -vvv --ask-sudo-pass --ask-vault-pass --vault-password-file=#{File.expand_path(__FILE__)} --tags=db,www --skip-tags=foo,bar --limit='machine*:&vagrant:!that_one' --start-at-task='an awesome task' playbook.yml") + expect(full_command).to eq("PYTHONUNBUFFERED=1 ANSIBLE_HOST_KEY_CHECKING=true ANSIBLE_FORCE_COLOR=true ANSIBLE_SSH_ARGS='-o IdentitiesOnly=yes -o IdentityFile=/my/key1 -o IdentityFile=/my/key2 -o ForwardAgent=yes -o ControlMaster=no -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --user=testuser --connection=ssh --timeout=30 --limit='machine*:&vagrant:!that_one' --inventory-file=#{generated_inventory_dir} --extra-vars=@#{File.expand_path(__FILE__)} --sudo --sudo-user=deployer -vvv --ask-sudo-pass --ask-vault-pass --vault-password-file=#{File.expand_path(__FILE__)} --tags=db,www --skip-tags=foo,bar --start-at-task='an awesome task' --why-not --su-user=foot --ask-su-pass --limit='all' --private-key=./myself.key playbook.yml") } end end + # + # Special cases related to the VM provider context + # + + context "with Docker provider on a non-Linux host" do + + let(:fake_host_ssh_info) {{ + private_key_path: ['/path/to/docker/host/key'], + username: 'boot9docker', + host: '127.0.0.1', + port: 2299 + }} + let(:fake_host_vm) { + double("host_vm").tap do |h| + h.stub(ssh_info: fake_host_ssh_info) + end + } + + before do + machine.stub(provider_name: :docker) + machine.provider.stub(host_vm?: true) + machine.provider.stub(host_vm: fake_host_vm) + end + + it "uses an SSH ProxyCommand to reach the VM" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + cmd_opts = args.last + expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o ProxyCommand='ssh boot9docker@127.0.0.1 -p 2299 -i /path/to/docker/host/key -o Compression=yes -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no exec nc %h %p 2>/dev/null'") + } + end + end + + # + # Special cases related to the Vagrant Host operating system in use + # + + context "with a Solaris-like host" do + before do + Vagrant::Util::Platform.stub(solaris?: true) + end + + it "does not set IdentitiesOnly=yes in ANSIBLE_SSH_ARGS" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + cmd_opts = args.last + expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to_not include("-o IdentitiesOnly=yes") + + # Note: + # The expectation below is a workaround to a possible misuse (or bug) in RSpec/Ruby stack. + # If 'args' variable is not required by in this block, the "Vagrant::Util::Subprocess).to receive" + # surprisingly expects to receive "no args". + # This problem can be "solved" by using args the "unnecessary" (but harmless) expectation below: + expect(true).to be_true + } + end + + describe "and with host_key_checking option enabled" do + it "does not set ANSIBLE_SSH_ARGS environment variable" do + config.host_key_checking = true + + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + cmd_opts = args.last + expect(cmd_opts[:env]).to_not include('ANSIBLE_SSH_ARGS') + + # Note: + # The expectation below is a workaround to a possible misuse (or bug) in RSpec/Ruby stack. + # If 'args' variable is not required by in this block, the "Vagrant::Util::Subprocess).to receive" + # surprisingly expects to receive "no args". + # This problem can be "solved" by using args the "unnecessary" (but harmless) expectation below: + expect(true).to be_true + } + end + end + + end end end diff --git a/test/unit/plugins/provisioners/chef/command_builder_test.rb b/test/unit/plugins/provisioners/chef/command_builder_test.rb index 3fb53693b..655c9041f 100644 --- a/test/unit/plugins/provisioners/chef/command_builder_test.rb +++ b/test/unit/plugins/provisioners/chef/command_builder_test.rb @@ -8,97 +8,110 @@ describe VagrantPlugins::Chef::CommandBuilder do let(:chef_config) { double("chef_config") } before(:each) do - allow(chef_config).to receive(:provisioning_path).and_return('/tmp/vagrant-chef-1') + allow(chef_config).to receive(:provisioning_path).and_return("/tmp/vagrant-chef-1") allow(chef_config).to receive(:arguments).and_return(nil) allow(chef_config).to receive(:binary_env).and_return(nil) allow(chef_config).to receive(:binary_path).and_return(nil) allow(chef_config).to receive(:binary_env).and_return(nil) + allow(chef_config).to receive(:log_level).and_return(:info) end - describe '.initialize' do - it 'should raise when chef type is not client or solo' do + describe ".initialize" do + it "raises an error when chef type is not client or solo" do expect { VagrantPlugins::Chef::CommandBuilder.new(chef_config, :client_bad) }. to raise_error end + + it "does not raise an error for :client" do + expect { + VagrantPlugins::Chef::CommandBuilder.new(:client, chef_config) + }.to_not raise_error + end + + it "does not raise an error for :solo" do + expect { + VagrantPlugins::Chef::CommandBuilder.new(:solo, chef_config) + }.to_not raise_error + end end - describe 'build_command' do - describe 'windows' do + describe "#command" do + describe "windows" do subject do - VagrantPlugins::Chef::CommandBuilder.new(chef_config, :client, true) + VagrantPlugins::Chef::CommandBuilder.new(:client, chef_config, windows: true) end it "executes the chef-client in PATH by default" do - expect(subject.build_command()).to match(/^chef-client/) + expect(subject.command).to match(/^chef-client/) end it "executes the chef-client using full path if binary_path is specified" do allow(chef_config).to receive(:binary_path).and_return( "c:\\opscode\\chef\\bin\\chef-client") - expect(subject.build_command()).to match(/^c:\\opscode\\chef\\bin\\chef-client\\chef-client/) + expect(subject.command).to match(/^c:\\opscode\\chef\\bin\\chef-client\\chef-client/) end it "builds a guest friendly client.rb path" do - expect(subject.build_command()).to include( - '-c c:\\tmp\\vagrant-chef-1\\client.rb') + expect(subject.command).to include( + '--config c:\\tmp\\vagrant-chef-1\\client.rb') end it "builds a guest friendly solo.json path" do - expect(subject.build_command()).to include( - '-j c:\\tmp\\vagrant-chef-1\\dna.json') + expect(subject.command).to include( + '--json-attributes c:\\tmp\\vagrant-chef-1\\dna.json') end - it 'includes Chef arguments if specified' do - allow(chef_config).to receive(:arguments).and_return("-l DEBUG") - expect(subject.build_command()).to include( - '-l DEBUG') + it "includes Chef arguments if specified" do + allow(chef_config).to receive(:arguments).and_return("bacon pants") + expect(subject.command).to include( + " bacon pants") end - it 'includes --no-color if UI is not colored' do - expect(subject.build_command()).to include( - ' --no-color') + it "includes --no-color if UI is not colored" do + expect(subject.command).to include( + " --no-color") end end - describe 'linux' do + describe "linux" do subject do - VagrantPlugins::Chef::CommandBuilder.new(chef_config, :client, false) + VagrantPlugins::Chef::CommandBuilder.new(:client, chef_config, windows: false) end it "executes the chef-client in PATH by default" do - expect(subject.build_command()).to match(/^chef-client/) + expect(subject.command).to match(/^chef-client/) end it "executes the chef-client using full path if binary_path is specified" do allow(chef_config).to receive(:binary_path).and_return( "/opt/chef/chef-client") - expect(subject.build_command()).to match(/^\/opt\/chef\/chef-client/) + expect(subject.command).to match(/^\/opt\/chef\/chef-client/) end it "builds a guest friendly client.rb path" do - expect(subject.build_command()).to include( - '-c /tmp/vagrant-chef-1/client.rb') + expect(subject.command).to include( + "--config /tmp/vagrant-chef-1/client.rb") end it "builds a guest friendly solo.json path" do - expect(subject.build_command()).to include( - '-j /tmp/vagrant-chef-1/dna.json') + expect(subject.command).to include( + "--json-attributes /tmp/vagrant-chef-1/dna.json") end - it 'includes Chef arguments if specified' do - allow(chef_config).to receive(:arguments).and_return("-l DEBUG") - expect(subject.build_command()).to include( - '-l DEBUG') + it "includes Chef arguments if specified" do + allow(chef_config).to receive(:arguments).and_return("bacon") + expect(subject.command).to include( + " bacon") end - it 'includes --no-color if UI is not colored' do - expect(subject.build_command()).to include( - ' --no-color') + it "includes --no-color if UI is not colored" do + expect(subject.command).to include( + " --no-color") end - it 'includes environment variables if specified' do + it "includes environment variables if specified" do allow(chef_config).to receive(:binary_env).and_return("ENVVAR=VAL") - expect(subject.build_command()).to match(/^ENVVAR=VAL /) + expect(subject.command).to match(/^ENVVAR=VAL /) end end end diff --git a/test/unit/plugins/provisioners/chef/config/base_runner_test.rb b/test/unit/plugins/provisioners/chef/config/base_runner_test.rb new file mode 100644 index 000000000..8bc6c6402 --- /dev/null +++ b/test/unit/plugins/provisioners/chef/config/base_runner_test.rb @@ -0,0 +1,262 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/provisioners/chef/config/base_runner") + +describe VagrantPlugins::Chef::Config::BaseRunner do + include_context "unit" + + subject { described_class.new } + + let(:machine) { double("machine") } + + describe "#arguments" do + it "defaults to nil" do + subject.finalize! + expect(subject.arguments).to be(nil) + end + end + + describe "#attempts" do + it "defaults to 1" do + subject.finalize! + expect(subject.attempts).to eq(1) + end + end + + describe "#custom_config_path" do + it "defaults to nil" do + subject.finalize! + expect(subject.custom_config_path).to be(nil) + end + end + + describe "#environment" do + it "defaults to nil" do + subject.finalize! + expect(subject.environment).to be(nil) + end + end + + describe "#encrypted_data_bag_secret_key_path" do + it "defaults to nil" do + subject.finalize! + expect(subject.encrypted_data_bag_secret_key_path).to be(nil) + end + end + + describe "#formatter" do + it "defaults to nil" do + subject.finalize! + expect(subject.formatter).to be(nil) + end + end + + describe "#http_proxy" do + it "defaults to nil" do + subject.finalize! + expect(subject.http_proxy).to be(nil) + end + end + + describe "#http_proxy_user" do + it "defaults to nil" do + subject.finalize! + expect(subject.http_proxy_user).to be(nil) + end + end + + describe "#http_proxy_pass" do + it "defaults to nil" do + subject.finalize! + expect(subject.http_proxy_pass).to be(nil) + end + end + + describe "#https_proxy" do + it "defaults to nil" do + subject.finalize! + expect(subject.https_proxy).to be(nil) + end + end + + describe "#https_proxy_user" do + it "defaults to nil" do + subject.finalize! + expect(subject.https_proxy_user).to be(nil) + end + end + + describe "#https_proxy_pass" do + it "defaults to nil" do + subject.finalize! + expect(subject.https_proxy_pass).to be(nil) + end + end + + describe "#log_level" do + it "defaults to :info" do + subject.finalize! + expect(subject.log_level).to be(:info) + end + + it "is converted to a symbol" do + subject.log_level = "foo" + subject.finalize! + expect(subject.log_level).to eq(:foo) + end + end + + describe "#no_proxy" do + it "defaults to nil" do + subject.finalize! + expect(subject.no_proxy).to be(nil) + end + end + + describe "#node_name" do + it "defaults to nil" do + subject.finalize! + expect(subject.node_name).to be(nil) + end + end + + describe "#provisioning_path" do + it "defaults to nil" do + subject.finalize! + expect(subject.provisioning_path).to be(nil) + end + end + + describe "#file_backup_path" do + it "defaults to nil" do + subject.finalize! + expect(subject.file_backup_path).to be(nil) + end + end + + describe "#file_cache_path" do + it "defaults to nil" do + subject.finalize! + expect(subject.file_cache_path).to be(nil) + end + end + + describe "#verbose_logging" do + it "defaults to false" do + subject.finalize! + expect(subject.verbose_logging).to be(false) + end + end + + describe "#enable_reporting" do + it "defaults to true" do + subject.finalize! + expect(subject.enable_reporting).to be(true) + end + end + + describe "#run_list" do + it "defaults to an empty array" do + subject.finalize! + expect(subject.run_list).to be_a(Array) + expect(subject.run_list).to be_empty + end + end + + describe "#json" do + it "defaults to an empty hash" do + subject.finalize! + expect(subject.json).to be_a(Hash) + expect(subject.json).to be_empty + end + end + + describe "#add_recipe" do + context "when the prefix is given" do + it "adds the value to the run_list" do + subject.add_recipe("recipe[foo::bar]") + expect(subject.run_list).to eq %w(recipe[foo::bar]) + end + end + + context "when the prefix is not given" do + it "adds the prefixed value to the run_list" do + subject.add_recipe("foo::bar") + expect(subject.run_list).to eq %w(recipe[foo::bar]) + end + end + end + + describe "#add_role" do + context "when the prefix is given" do + it "adds the value to the run_list" do + subject.add_role("role[foo]") + expect(subject.run_list).to eq %w(role[foo]) + end + end + + context "when the prefix is not given" do + it "adds the prefixed value to the run_list" do + subject.add_role("foo") + expect(subject.run_list).to eq %w(role[foo]) + end + end + end + + describe "#validate_base" do + context "when #custom_config_path does not exist" do + let(:path) { "/path/to/file" } + + before do + allow(File).to receive(:file?) + .with(path) + .and_return(false) + + allow(machine).to receive(:env) + .and_return(double("env", + root_path: "", + )) + end + + it "returns an error" do + subject.custom_config_path = path + subject.finalize! + + expect(subject.validate_base(machine)) + .to eq ['Path specified for "custom_config_path" does not exist.'] + end + end + end + + describe "#merge" do + it "merges the json hash" do + a = described_class.new.tap do |i| + i.json = { "foo" => "bar" } + end + b = described_class.new.tap do |i| + i.json = { "zip" => "zap" } + end + + result = a.merge(b) + expect(result.json).to eq( + "foo" => "bar", + "zip" => "zap", + ) + end + + it "appends the run_list array" do + a = described_class.new.tap do |i| + i.run_list = ["recipe[foo::bar]"] + end + b = described_class.new.tap do |i| + i.run_list = ["recipe[zip::zap]"] + end + + result = a.merge(b) + expect(result.run_list).to eq %w( + recipe[foo::bar] + recipe[zip::zap] + ) + end + end +end diff --git a/test/unit/plugins/provisioners/chef/config/base_test.rb b/test/unit/plugins/provisioners/chef/config/base_test.rb new file mode 100644 index 000000000..4b018f11b --- /dev/null +++ b/test/unit/plugins/provisioners/chef/config/base_test.rb @@ -0,0 +1,78 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/provisioners/chef/config/base") + +describe VagrantPlugins::Chef::Config::Base do + include_context "unit" + + subject { described_class.new } + + let(:machine) { double("machine") } + + describe "#binary_path" do + it "defaults to nil" do + subject.finalize! + expect(subject.binary_path).to be(nil) + end + end + + describe "#binary_env" do + it "defaults to nil" do + subject.finalize! + expect(subject.binary_env).to be(nil) + end + end + + describe "#install" do + it "defaults to true" do + subject.finalize! + expect(subject.install).to be(true) + end + + it "is converted to a symbol" do + subject.install = "force" + subject.finalize! + expect(subject.install).to eq(:force) + end + end + + describe "#log_level" do + it "defaults to :info" do + subject.finalize! + expect(subject.log_level).to be(:info) + end + + it "is converted to a symbol" do + subject.log_level = "foo" + subject.finalize! + expect(subject.log_level).to eq(:foo) + end + end + + describe "#prerelease" do + it "defaults to true" do + subject.finalize! + expect(subject.prerelease).to be(false) + end + end + + describe "#version" do + it "defaults to :latest" do + subject.finalize! + expect(subject.version).to eq(:latest) + end + + it "converts the string 'latest' to a symbol" do + subject.version = "latest" + subject.finalize! + expect(subject.version).to eq(:latest) + end + end + + describe "#installer_download_path" do + it "defaults to nil" do + subject.finalize! + expect(subject.installer_download_path).to be(nil) + end + end +end diff --git a/test/unit/plugins/provisioners/chef/config/chef_apply_test.rb b/test/unit/plugins/provisioners/chef/config/chef_apply_test.rb new file mode 100644 index 000000000..135c04213 --- /dev/null +++ b/test/unit/plugins/provisioners/chef/config/chef_apply_test.rb @@ -0,0 +1,85 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/provisioners/chef/config/chef_apply") + +describe VagrantPlugins::Chef::Config::ChefApply do + include_context "unit" + + subject { described_class.new } + + let(:machine) { double("machine") } + + def chef_error(key, options = {}) + I18n.t("vagrant.provisioners.chef.#{key}", options) + end + + describe "#recipe" do + it "defaults to nil" do + subject.finalize! + expect(subject.recipe).to be(nil) + end + end + + describe "#upload_path" do + it "defaults to /tmp/vagrant-chef-apply.rb" do + subject.finalize! + expect(subject.upload_path).to eq("/tmp/vagrant-chef-apply") + end + end + + describe "#validate" do + before do + allow(machine).to receive(:env) + .and_return(double("env", + root_path: "", + )) + + subject.recipe = <<-EOH + package "foo" + EOH + end + + let(:result) { subject.validate(machine) } + let(:errors) { result["chef apply provisioner"] } + + context "when the recipe is nil" do + it "returns an error" do + subject.recipe = nil + subject.finalize! + expect(errors).to include chef_error("recipe_empty") + end + end + + context "when the recipe is empty" do + it "returns an error" do + subject.recipe = " " + subject.finalize! + expect(errors).to include chef_error("recipe_empty") + end + end + + context "when the log_level is an empty array" do + it "returns an error" do + subject.log_level = " " + subject.finalize! + expect(errors).to include chef_error("log_level_empty") + end + end + + context "when the upload_path is nil" do + it "returns an error" do + subject.upload_path = nil + subject.finalize! + expect(errors).to include chef_error("upload_path_empty") + end + end + + context "when the upload_path is an empty array" do + it "returns an error" do + subject.upload_path = " " + subject.finalize! + expect(errors).to include chef_error("upload_path_empty") + end + end + end +end diff --git a/test/unit/plugins/provisioners/chef/config/chef_client_test.rb b/test/unit/plugins/provisioners/chef/config/chef_client_test.rb new file mode 100644 index 000000000..25034d019 --- /dev/null +++ b/test/unit/plugins/provisioners/chef/config/chef_client_test.rb @@ -0,0 +1,136 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/provisioners/chef/config/chef_client") + +describe VagrantPlugins::Chef::Config::ChefClient do + include_context "unit" + + subject { described_class.new } + + let(:machine) { double("machine") } + + describe "#chef_server_url" do + it "defaults to nil" do + subject.finalize! + expect(subject.chef_server_url).to be(nil) + end + end + + describe "#client_key_path" do + it "defaults to nil" do + subject.finalize! + expect(subject.client_key_path).to be(nil) + end + end + + describe "#delete_client" do + it "defaults to false" do + subject.finalize! + expect(subject.delete_client).to be(false) + end + end + + describe "#delete_node" do + it "defaults to false" do + subject.finalize! + expect(subject.delete_node).to be(false) + end + end + + describe "#validation_key_path" do + it "defaults to nil" do + subject.finalize! + expect(subject.validation_key_path).to be(nil) + end + end + + describe "#validation_client_name" do + it "defaults to chef-validator" do + subject.finalize! + expect(subject.validation_client_name).to eq("chef-validator") + end + end + + describe "#validate" do + before do + allow(machine).to receive(:env) + .and_return(double("env", + root_path: "", + )) + + subject.chef_server_url = "https://example.com" + subject.validation_key_path = "/path/to/key.pem" + end + + let(:result) { subject.validate(machine) } + let(:errors) { result["chef client provisioner"] } + + context "when the chef_server_url is nil" do + it "returns an error" do + subject.chef_server_url = nil + subject.finalize! + expect(errors).to eq([I18n.t("vagrant.config.chef.server_url_empty")]) + end + end + + context "when the chef_server_url is blank" do + it "returns an error" do + subject.chef_server_url = " " + subject.finalize! + expect(errors).to eq([I18n.t("vagrant.config.chef.server_url_empty")]) + end + end + + context "when the validation_key_path is nil" do + it "returns an error" do + subject.validation_key_path = nil + subject.finalize! + expect(errors).to eq([I18n.t("vagrant.config.chef.validation_key_path")]) + end + end + + context "when the validation_key_path is blank" do + it "returns an error" do + subject.validation_key_path = " " + subject.finalize! + expect(errors).to eq([I18n.t("vagrant.config.chef.validation_key_path")]) + end + end + + context "when #delete_client is given" do + before { subject.delete_client = true } + + context "when knife does not exist" do + before do + allow(Vagrant::Util::Which) + .to receive(:which) + .with("knife") + .and_return(nil) + end + + it "returns an error" do + subject.finalize! + expect(errors).to eq([I18n.t("vagrant.chef_config_knife_not_found")]) + end + end + end + + context "when #delete_node is given" do + before { subject.delete_node = true } + + context "when knife does not exist" do + before do + allow(Vagrant::Util::Which) + .to receive(:which) + .with("knife") + .and_return(nil) + end + + it "returns an error" do + subject.finalize! + expect(errors).to eq([I18n.t("vagrant.chef_config_knife_not_found")]) + end + end + end + end +end diff --git a/test/unit/plugins/provisioners/chef/config/chef_solo_test.rb b/test/unit/plugins/provisioners/chef/config/chef_solo_test.rb new file mode 100644 index 000000000..d6a2a03af --- /dev/null +++ b/test/unit/plugins/provisioners/chef/config/chef_solo_test.rb @@ -0,0 +1,147 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/provisioners/chef/config/chef_solo") + +describe VagrantPlugins::Chef::Config::ChefSolo do + include_context "unit" + + subject { described_class.new } + + let(:machine) { double("machine") } + + describe "#cookbooks_path" do + it "defaults to something" do + subject.finalize! + expect(subject.cookbooks_path).to eq([ + [:host, "cookbooks"], + [:vm, "cookbooks"], + ]) + end + end + + describe "#data_bags_path" do + it "defaults to an empty array" do + subject.finalize! + expect(subject.data_bags_path).to be_a(Array) + expect(subject.data_bags_path).to be_empty + end + end + + describe "#environments_path" do + it "defaults to an empty array" do + subject.finalize! + expect(subject.environments_path).to be_a(Array) + expect(subject.environments_path).to be_empty + end + + it "merges deeply nested paths" do + subject.environments_path = ["/foo", "/bar", ["/zip"]] + subject.finalize! + expect(subject.environments_path) + .to eq([:host, :host, :host].zip %w(/foo /bar /zip)) + end + end + + describe "#recipe_url" do + it "defaults to nil" do + subject.finalize! + expect(subject.recipe_url).to be(nil) + end + end + + describe "#roles_path" do + it "defaults to an empty array" do + subject.finalize! + expect(subject.roles_path).to be_a(Array) + expect(subject.roles_path).to be_empty + end + end + + describe "#synced_folder_type" do + it "defaults to nil" do + subject.finalize! + expect(subject.synced_folder_type).to be(nil) + end + end + + describe "#validate" do + before do + allow(machine).to receive(:env) + .and_return(double("env", + root_path: "", + )) + + subject.cookbooks_path = ["/cookbooks", "/more/cookbooks"] + end + + let(:result) { subject.validate(machine) } + let(:errors) { result["chef solo provisioner"] } + + context "when the cookbooks_path is nil" do + it "returns an error" do + subject.cookbooks_path = nil + subject.finalize! + expect(errors).to eq [I18n.t("vagrant.config.chef.cookbooks_path_empty")] + end + end + + context "when the cookbooks_path is an empty array" do + it "returns an error" do + subject.cookbooks_path = [] + subject.finalize! + expect(errors).to eq [I18n.t("vagrant.config.chef.cookbooks_path_empty")] + end + end + + context "when the cookbooks_path is an array with nil" do + it "returns an error" do + subject.cookbooks_path = [nil, nil] + subject.finalize! + expect(errors).to eq [I18n.t("vagrant.config.chef.cookbooks_path_empty")] + end + end + + context "when environments is given" do + before do + subject.environment = "production" + end + + context "when the environments_path is nil" do + it "returns an error" do + subject.environments_path = nil + subject.finalize! + expect(errors).to eq [I18n.t("vagrant.config.chef.environment_path_required")] + end + end + + context "when the environments_path is an empty array" do + it "returns an error" do + subject.environments_path = [] + subject.finalize! + expect(errors).to eq [I18n.t("vagrant.config.chef.environment_path_required")] + end + end + + context "when the environments_path is an array with nil" do + it "returns an error" do + subject.environments_path = [nil, nil] + subject.finalize! + expect(errors).to eq [I18n.t("vagrant.config.chef.environment_path_required")] + end + end + + context "when the environments_path does not exist" do + it "returns an error" do + env_path = "/path/to/environments/that/will/never/exist" + subject.environments_path = env_path + subject.finalize! + expect(errors).to eq [ + I18n.t("vagrant.config.chef.environment_path_missing", + path: env_path + ) + ] + end + end + end + end +end diff --git a/test/unit/plugins/provisioners/chef/config/chef_zero_test.rb b/test/unit/plugins/provisioners/chef/config/chef_zero_test.rb new file mode 100644 index 000000000..2f9cd82aa --- /dev/null +++ b/test/unit/plugins/provisioners/chef/config/chef_zero_test.rb @@ -0,0 +1,140 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/provisioners/chef/config/chef_zero") + +describe VagrantPlugins::Chef::Config::ChefZero do + include_context "unit" + + subject { described_class.new } + + let(:machine) { double("machine") } + + describe "#cookbooks_path" do + it "defaults to something" do + subject.finalize! + expect(subject.cookbooks_path).to eq([ + [:host, "cookbooks"], + [:vm, "cookbooks"], + ]) + end + end + + describe "#data_bags_path" do + it "defaults to an empty array" do + subject.finalize! + expect(subject.data_bags_path).to be_a(Array) + expect(subject.data_bags_path).to be_empty + end + end + + describe "#environments_path" do + it "defaults to an empty array" do + subject.finalize! + expect(subject.environments_path).to be_a(Array) + expect(subject.environments_path).to be_empty + end + + it "merges deeply nested paths" do + subject.environments_path = ["/foo", "/bar", ["/zip"]] + subject.finalize! + expect(subject.environments_path) + .to eq([:host, :host, :host].zip %w(/foo /bar /zip)) + end + end + + describe "#roles_path" do + it "defaults to an empty array" do + subject.finalize! + expect(subject.roles_path).to be_a(Array) + expect(subject.roles_path).to be_empty + end + end + + describe "#synced_folder_type" do + it "defaults to nil" do + subject.finalize! + expect(subject.synced_folder_type).to be(nil) + end + end + + describe "#validate" do + before do + allow(machine).to receive(:env) + .and_return(double("env", + root_path: "", + )) + + subject.cookbooks_path = ["/cookbooks", "/more/cookbooks"] + end + + let(:result) { subject.validate(machine) } + let(:errors) { result["chef zero provisioner"] } + + context "when the cookbooks_path is nil" do + it "returns an error" do + subject.cookbooks_path = nil + subject.finalize! + expect(errors).to eq [I18n.t("vagrant.config.chef.cookbooks_path_empty")] + end + end + + context "when the cookbooks_path is an empty array" do + it "returns an error" do + subject.cookbooks_path = [] + subject.finalize! + expect(errors).to eq [I18n.t("vagrant.config.chef.cookbooks_path_empty")] + end + end + + context "when the cookbooks_path is an array with nil" do + it "returns an error" do + subject.cookbooks_path = [nil, nil] + subject.finalize! + expect(errors).to eq [I18n.t("vagrant.config.chef.cookbooks_path_empty")] + end + end + + context "when environments is given" do + before do + subject.environment = "production" + end + + context "when the environments_path is nil" do + it "returns an error" do + subject.environments_path = nil + subject.finalize! + expect(errors).to eq [I18n.t("vagrant.config.chef.environment_path_required")] + end + end + + context "when the environments_path is an empty array" do + it "returns an error" do + subject.environments_path = [] + subject.finalize! + expect(errors).to eq [I18n.t("vagrant.config.chef.environment_path_required")] + end + end + + context "when the environments_path is an array with nil" do + it "returns an error" do + subject.environments_path = [nil, nil] + subject.finalize! + expect(errors).to eq [I18n.t("vagrant.config.chef.environment_path_required")] + end + end + + context "when the environments_path does not exist" do + it "returns an error" do + env_path = "/path/to/environments/that/will/never/exist" + subject.environments_path = env_path + subject.finalize! + expect(errors).to eq [ + I18n.t("vagrant.config.chef.environment_path_missing", + path: env_path + ) + ] + end + end + end + end +end diff --git a/test/unit/plugins/provisioners/chef/omnibus_test.rb b/test/unit/plugins/provisioners/chef/omnibus_test.rb new file mode 100644 index 000000000..b053f94bc --- /dev/null +++ b/test/unit/plugins/provisioners/chef/omnibus_test.rb @@ -0,0 +1,55 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/provisioners/chef/omnibus") + +describe VagrantPlugins::Chef::Omnibus do + let(:prefix) { "curl -sL #{described_class.const_get(:OMNITRUCK)}" } + + let(:version) { :latest } + let(:prerelease) { false } + let(:download_path) { nil } + + let(:build_command) { described_class.build_command(version, prerelease, download_path) } + + context "when prerelease is given" do + let(:prerelease) { true } + + it "returns the correct command" do + expect(build_command).to eq("#{prefix} | sudo bash -s -- -p") + end + end + + context "when download_path is given" do + let(:download_path) { '/tmp/path/to/omnibuses' } + + it "returns the correct command" do + expect(build_command).to eq("#{prefix} | sudo bash -s -- -d \"/tmp/path/to/omnibuses\"") + end + end + + context "when version is :latest" do + let(:version) { :latest } + + it "returns the correct command" do + expect(build_command).to eq("#{prefix} | sudo bash") + end + end + + context "when version is a string" do + let(:version) { "1.2.3" } + + it "returns the correct command" do + expect(build_command).to eq("#{prefix} | sudo bash -s -- -v \"1.2.3\"") + end + end + + context "when prerelease and version and download_path are given" do + let(:version) { "1.2.3" } + let(:prerelease) { true } + let(:download_path) { "/some/path" } + + it "returns the correct command" do + expect(build_command).to eq("#{prefix} | sudo bash -s -- -p -v \"1.2.3\" -d \"/some/path\"") + end + end +end diff --git a/test/unit/plugins/provisioners/docker/config_test.rb b/test/unit/plugins/provisioners/docker/config_test.rb index e34c24bba..e12f883c4 100644 --- a/test/unit/plugins/provisioners/docker/config_test.rb +++ b/test/unit/plugins/provisioners/docker/config_test.rb @@ -59,11 +59,13 @@ describe VagrantPlugins::DockerProvisioner::Config do auto_assign_name: true, image: "foo", daemonize: false, + restart: "always", }) expect(cs["bar"]).to eq({ auto_assign_name: true, image: "bar", daemonize: true, + restart: "always", }) end @@ -102,6 +104,7 @@ describe VagrantPlugins::DockerProvisioner::Config do auto_assign_name: true, daemonize: true, image: "foo", + restart: "always", } }) end @@ -115,6 +118,7 @@ describe VagrantPlugins::DockerProvisioner::Config do auto_assign_name: false, daemonize: true, image: "foo", + restart: "always", } }) end @@ -128,6 +132,7 @@ describe VagrantPlugins::DockerProvisioner::Config do auto_assign_name: true, daemonize: false, image: "foo", + restart: "always", } }) end diff --git a/test/unit/plugins/provisioners/file/config_test.rb b/test/unit/plugins/provisioners/file/config_test.rb index e41cac966..4302a0a9c 100644 --- a/test/unit/plugins/provisioners/file/config_test.rb +++ b/test/unit/plugins/provisioners/file/config_test.rb @@ -7,7 +7,13 @@ describe VagrantPlugins::FileUpload::Config do subject { described_class.new } - let(:machine) { double("machine") } + let(:env) do + iso_env = isolated_environment + iso_env.vagrantfile("") + iso_env.create_vagrant_env + end + + let(:machine) { double("machine", env: env) } describe "#validate" do it "returns an error if destination is not specified" do @@ -33,7 +39,7 @@ describe VagrantPlugins::FileUpload::Config do end it "returns an error if source file does not exist" do - non_existing_file = "this/does/not/exist" + non_existing_file = "/this/does/not/exist" subject.source = non_existing_file subject.destination = "/tmp/foo" @@ -58,7 +64,10 @@ describe VagrantPlugins::FileUpload::Config do end it "passes with relative source path" do - existing_relative_path = __FILE__ + path = env.root_path.join("foo") + path.open("w+") { |f| f.write("hello") } + + existing_relative_path = "foo" subject.source = existing_relative_path subject.destination = "/tmp/foo" diff --git a/test/unit/plugins/provisioners/salt/config_test.rb b/test/unit/plugins/provisioners/salt/config_test.rb index c640a7497..24bf24796 100644 --- a/test/unit/plugins/provisioners/salt/config_test.rb +++ b/test/unit/plugins/provisioners/salt/config_test.rb @@ -60,5 +60,23 @@ describe VagrantPlugins::Salt::Config do expect(result[error_key]).to be_empty end end + + context "grains_config" do + it "fails if grains_config is set and missing" do + subject.grains_config = "/nope/still/not/here" + subject.finalize! + + result = subject.validate(machine) + expect(result[error_key]).to_not be_empty + end + + it "is valid if is set and not missing" do + subject.grains_config = File.expand_path(__FILE__) + subject.finalize! + + result = subject.validate(machine) + expect(result[error_key]).to be_empty + end + end end end diff --git a/test/unit/plugins/pushes/atlas/config_test.rb b/test/unit/plugins/pushes/atlas/config_test.rb new file mode 100644 index 000000000..64240f0a0 --- /dev/null +++ b/test/unit/plugins/pushes/atlas/config_test.rb @@ -0,0 +1,207 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/pushes/atlas/config") + +describe VagrantPlugins::AtlasPush::Config do + include_context "unit" + + before(:all) do + I18n.load_path << Vagrant.source_root.join("plugins/pushes/atlas/locales/en.yml") + I18n.reload! + end + + let(:machine) { double("machine") } + + around(:each) do |example| + with_temp_env("ATLAS_TOKEN" => nil) do + example.run + end + end + + before do + subject.token = "foo" + end + + describe "#address" do + it "defaults to nil" do + subject.finalize! + expect(subject.address).to be(nil) + end + end + + describe "#app" do + it "defaults to nil" do + subject.finalize! + expect(subject.app).to be(nil) + end + end + + describe "#dir" do + it "defaults to ." do + subject.finalize! + expect(subject.dir).to eq(".") + end + end + + describe "#vcs" do + it "defaults to true" do + subject.finalize! + expect(subject.vcs).to be(true) + end + end + + describe "#uploader_path" do + it "defaults to nil" do + subject.finalize! + expect(subject.uploader_path).to be(nil) + end + end + + describe "#validate" do + before do + allow(machine).to receive(:env) + .and_return(double("env", + root_path: "", + data_dir: Pathname.new(""), + )) + + subject.app = "sethvargo/bacon" + subject.dir = "." + subject.vcs = true + subject.uploader_path = "uploader" + end + + let(:result) { subject.validate(machine) } + let(:errors) { result["Atlas push"] } + + context "when the token is missing" do + context "when a vagrant-login token exists" do + before do + allow(subject).to receive(:token_from_vagrant_login) + .and_return("token_from_vagrant_login") + end + + it "uses the token from vagrant-login" do + subject.token = "" + subject.finalize! + expect(errors).to be_empty + expect(subject.token).to eq("token_from_vagrant_login") + end + end + + context "when a token is given in the Vagrantfile" do + before do + allow(subject).to receive(:token_from_vagrant_login) + .and_return("token_from_vagrant_login") + end + + it "uses the token in the Vagrantfile" do + subject.token = "token_from_vagrantfile" + subject.finalize! + expect(errors).to be_empty + expect(subject.token).to eq("token_from_vagrantfile") + end + end + + context "when a token is in the environment" do + it "uses the token in the Vagrantfile" do + with_temp_env("ATLAS_TOKEN" => "foo") do + subject.finalize! + end + + expect(errors).to be_empty + expect(subject.token).to eq("foo") + end + end + + context "when no token is given" do + before do + allow(subject).to receive(:token_from_vagrant_login) + .and_return(nil) + end + + it "returns an error" do + subject.token = "" + subject.finalize! + expect(errors).to include(I18n.t("atlas_push.errors.missing_token")) + end + end + end + + context "when the app is missing" do + it "returns an error" do + subject.app = "" + subject.finalize! + expect(errors).to include(I18n.t("atlas_push.errors.missing_attribute", + attribute: "app", + )) + end + end + + context "when the dir is missing" do + it "returns an error" do + subject.dir = "" + subject.finalize! + expect(errors).to include(I18n.t("atlas_push.errors.missing_attribute", + attribute: "dir", + )) + end + end + + context "when the vcs is missing" do + it "does not return an error" do + subject.vcs = "" + subject.finalize! + expect(errors).to be_empty + end + end + + context "when the uploader_path is missing" do + it "returns an error" do + subject.uploader_path = "" + subject.finalize! + expect(errors).to be_empty + end + end + end + + describe "#merge" do + context "when includes are given" do + let(:one) { described_class.new } + let(:two) { described_class.new } + + it "merges the result" do + one.includes = %w(a b c) + two.includes = %w(c d e) + result = one.merge(two) + expect(result.includes).to eq(%w(a b c d e)) + end + end + + context "when excludes are given" do + let(:one) { described_class.new } + let(:two) { described_class.new } + + it "merges the result" do + one.excludes = %w(a b c) + two.excludes = %w(c d e) + result = one.merge(two) + expect(result.excludes).to eq(%w(a b c d e)) + end + end + end + + describe "#include" do + it "adds the item to the list" do + subject.include("me") + expect(subject.includes).to include("me") + end + end + + describe "#exclude" do + it "adds the item to the list" do + subject.exclude("not me") + expect(subject.excludes).to include("not me") + end + end +end diff --git a/test/unit/plugins/pushes/atlas/push_test.rb b/test/unit/plugins/pushes/atlas/push_test.rb new file mode 100644 index 000000000..72eb46057 --- /dev/null +++ b/test/unit/plugins/pushes/atlas/push_test.rb @@ -0,0 +1,176 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/pushes/atlas/config") +require Vagrant.source_root.join("plugins/pushes/atlas/push") + +describe VagrantPlugins::AtlasPush::Push do + include_context "unit" + + let(:bin) { VagrantPlugins::AtlasPush::Push::UPLOADER_BIN } + + let(:env) do + iso_env = isolated_environment + iso_env.vagrantfile("") + iso_env.create_vagrant_env + end + + let(:config) do + VagrantPlugins::AtlasPush::Config.new.tap do |c| + c.finalize! + end + end + + subject { described_class.new(env, config) } + + before do + # Stub this right away to avoid real execs + allow(Vagrant::Util::SafeExec).to receive(:exec) + end + + describe "#push" do + it "pushes with the uploader" do + allow(subject).to receive(:uploader_path).and_return("foo") + + expect(subject).to receive(:execute).with("foo") + + subject.push + end + + it "raises an exception if the uploader couldn't be found" do + expect(subject).to receive(:uploader_path).and_return(nil) + + expect { subject.push }.to raise_error( + VagrantPlugins::AtlasPush::Errors::UploaderNotFound) + end + end + + describe "#execute" do + let(:app) { "foo/bar" } + + before do + config.app = app + end + + it "sends the basic flags" do + expect(Vagrant::Util::SafeExec).to receive(:exec). + with("foo", "-vcs", app, env.root_path.to_s) + + subject.execute("foo") + end + + it "doesn't send VCS if disabled" do + expect(Vagrant::Util::SafeExec).to receive(:exec). + with("foo", app, env.root_path.to_s) + + config.vcs = false + subject.execute("foo") + end + + it "sends includes" do + expect(Vagrant::Util::SafeExec).to receive(:exec). + with("foo", "-vcs", "-include", "foo", "-include", + "bar", app, env.root_path.to_s) + + config.includes = ["foo", "bar"] + subject.execute("foo") + end + + it "sends excludes" do + expect(Vagrant::Util::SafeExec).to receive(:exec). + with("foo", "-vcs", "-exclude", "foo", "-exclude", + "bar", app, env.root_path.to_s) + + config.excludes = ["foo", "bar"] + subject.execute("foo") + end + + it "sends custom server address" do + expect(Vagrant::Util::SafeExec).to receive(:exec). + with("foo", "-vcs", "-address", "foo", app, env.root_path.to_s) + + config.address = "foo" + subject.execute("foo") + end + + it "sends custom token" do + expect(Vagrant::Util::SafeExec).to receive(:exec). + with("foo", "-vcs", "-token", "atlas_token", app, env.root_path.to_s) + + config.token = "atlas_token" + subject.execute("foo") + end + + context "when metadata is available" do + let(:env) do + iso_env = isolated_environment + iso_env.vagrantfile <<-EOH + Vagrant.configure(2) do |config| + config.vm.box = "hashicorp/precise64" + config.vm.box_url = "https://atlas.hashicorp.com/hashicorp/precise64" + end + EOH + iso_env.create_vagrant_env + end + + it "sends the metadata" do + expect(Vagrant::Util::SafeExec).to receive(:exec). + with("foo", "-vcs", "-metadata", "box=hashicorp/precise64", + "-metadata", "box_url=https://atlas.hashicorp.com/hashicorp/precise64", + "-token", "atlas_token", app, env.root_path.to_s) + + config.token = "atlas_token" + subject.execute("foo") + end + end + end + + describe "#uploader_path" do + it "should return the configured path if set" do + config.uploader_path = "foo" + expect(subject.uploader_path).to eq("foo") + end + + it "should look up the uploader via PATH if not set" do + allow(Vagrant).to receive(:in_installer?).and_return(false) + + expect(Vagrant::Util::Which).to receive(:which). + with(described_class.const_get(:UPLOADER_BIN)). + and_return("bar") + + expect(subject.uploader_path).to eq("bar") + end + + it "should look up the uploader in the embedded dir if installer" do + dir = temporary_dir + + allow(Vagrant).to receive(:in_installer?).and_return(true) + allow(Vagrant).to receive(:installer_embedded_dir).and_return(dir.to_s) + + bin_path = dir.join("bin", bin) + bin_path.dirname.mkpath + bin_path.open("w+") { |f| f.write("hi") } + + expect(subject.uploader_path).to eq(bin_path.to_s) + end + + it "should look up the uploader in the PATH if not in the installer" do + dir = temporary_dir + + allow(Vagrant).to receive(:in_installer?).and_return(true) + allow(Vagrant).to receive(:installer_embedded_dir).and_return(dir.to_s) + + expect(Vagrant::Util::Which).to receive(:which). + with(described_class.const_get(:UPLOADER_BIN)). + and_return("bar") + + expect(subject.uploader_path).to eq("bar") + end + + it "should return nil if its not found anywhere" do + allow(Vagrant).to receive(:in_installer?).and_return(false) + allow(Vagrant::Util::Which).to receive(:which).and_return(nil) + + expect(subject.uploader_path).to be_nil + end + end +end diff --git a/test/unit/plugins/pushes/ftp/adapter_test.rb b/test/unit/plugins/pushes/ftp/adapter_test.rb new file mode 100644 index 000000000..e929078cd --- /dev/null +++ b/test/unit/plugins/pushes/ftp/adapter_test.rb @@ -0,0 +1,111 @@ +require_relative "../../../base" +require "fake_ftp" + +require Vagrant.source_root.join("plugins/pushes/ftp/adapter") + +describe VagrantPlugins::FTPPush::Adapter do + include_context "unit" + + subject do + described_class.new("127.0.0.1:2345", "sethvargo", "bacon", + foo: "bar", + ) + end + + describe "#initialize" do + it "sets the instance variables" do + expect(subject.host).to eq("127.0.0.1") + expect(subject.port).to eq(2345) + expect(subject.username).to eq("sethvargo") + expect(subject.password).to eq("bacon") + expect(subject.options).to eq(foo: "bar") + expect(subject.server).to be(nil) + end + end + + describe "#parse_host" do + it "has a default value" do + allow(subject).to receive(:default_port) + .and_return(5555) + + result = subject.parse_host("127.0.0.1") + expect(result[0]).to eq("127.0.0.1") + expect(result[1]).to eq(5555) + end + end +end + +describe VagrantPlugins::FTPPush::FTPAdapter do + include_context "unit" + + before(:all) do + @server = FakeFtp::Server.new(21212, 21213) + @server.start + end + + after(:all) { @server.stop } + + let(:server) { @server } + + before { server.reset } + + subject do + described_class.new("127.0.0.1:#{server.port}", "sethvargo", "bacon") + end + + describe "#default_port" do + it "is 21" do + expect(subject.default_port).to eq(21) + end + end + + describe "#upload" do + before do + @dir = Dir.mktmpdir + FileUtils.touch("#{@dir}/file") + end + + after do + FileUtils.rm_rf(@dir) + end + + it "uploads the file" do + subject.connect do |ftp| + ftp.upload("#{@dir}/file", "/file") + end + + expect(server.files).to include("file") + end + + it "uploads in passive mode" do + subject.options[:passive] = true + subject.connect do |ftp| + ftp.upload("#{@dir}/file", "/file") + end + + expect(server.file("file")).to be_passive + end + end +end + +describe VagrantPlugins::FTPPush::SFTPAdapter do + include_context "unit" + + subject do + described_class.new("127.0.0.1:2345", "sethvargo", "bacon", + foo: "bar", + ) + end + + describe "#default_port" do + it "is 22" do + expect(subject.default_port).to eq(22) + end + end + + describe "#upload" do + it "uploads the file" do + pending "a way to mock an SFTP server" + end + end +end diff --git a/test/unit/plugins/pushes/ftp/config_test.rb b/test/unit/plugins/pushes/ftp/config_test.rb new file mode 100644 index 000000000..f66eeb791 --- /dev/null +++ b/test/unit/plugins/pushes/ftp/config_test.rb @@ -0,0 +1,171 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/pushes/ftp/config") + +describe VagrantPlugins::FTPPush::Config do + include_context "unit" + + before(:all) do + I18n.load_path << Vagrant.source_root.join("plugins/pushes/ftp/locales/en.yml") + I18n.reload! + end + + subject { described_class.new } + + let(:machine) { double("machine") } + + describe "#host" do + it "defaults to nil" do + subject.finalize! + expect(subject.host).to be(nil) + end + end + + describe "#username" do + it "defaults to nil" do + subject.finalize! + expect(subject.username).to be(nil) + end + end + + describe "#password" do + it "defaults to nil" do + subject.finalize! + expect(subject.password).to be(nil) + end + end + + describe "#passive" do + it "defaults to true" do + subject.finalize! + expect(subject.passive).to be(true) + end + end + + describe "#secure" do + it "defaults to false" do + subject.finalize! + expect(subject.secure).to be(false) + end + end + + describe "#destination" do + it "defaults to /" do + subject.finalize! + expect(subject.destination).to eq("/") + end + end + + describe "#dir" do + it "defaults to ." do + subject.finalize! + expect(subject.dir).to eq(".") + end + end + + describe "#merge" do + context "when includes are given" do + let(:one) { described_class.new } + let(:two) { described_class.new } + + it "merges the result" do + one.includes = %w(a b c) + two.includes = %w(c d e) + result = one.merge(two) + expect(result.includes).to eq(%w(a b c d e)) + end + end + + context "when excludes are given" do + let(:one) { described_class.new } + let(:two) { described_class.new } + + it "merges the result" do + one.excludes = %w(a b c) + two.excludes = %w(c d e) + result = one.merge(two) + expect(result.excludes).to eq(%w(a b c d e)) + end + end + end + + describe "#validate" do + before do + allow(machine).to receive(:env) + .and_return(double("env", + root_path: "", + )) + + subject.host = "ftp.example.com" + subject.username = "sethvargo" + subject.password = "bacon" + subject.destination = "/" + subject.dir = "." + end + + let(:result) { subject.validate(machine) } + let(:errors) { result["FTP push"] } + + context "when the host is missing" do + it "returns an error" do + subject.host = "" + subject.finalize! + expect(errors).to include(I18n.t("ftp_push.errors.missing_attribute", + attribute: "host", + )) + end + end + + context "when the username is missing" do + it "returns an error" do + subject.username = "" + subject.finalize! + expect(errors).to include(I18n.t("ftp_push.errors.missing_attribute", + attribute: "username", + )) + end + end + + context "when the password is missing" do + it "does not return an error" do + subject.password = "" + subject.finalize! + expect(errors).to be_empty + end + end + + context "when the destination is missing" do + it "returns an error" do + subject.destination = "" + subject.finalize! + expect(errors).to include(I18n.t("ftp_push.errors.missing_attribute", + attribute: "destination", + )) + end + end + + context "when the dir is missing" do + it "returns an error" do + subject.dir = "" + subject.finalize! + expect(errors).to include(I18n.t("ftp_push.errors.missing_attribute", + attribute: "dir", + )) + end + end + end + + describe "#include" do + it "adds the item to the list" do + subject.include("me") + expect(subject.includes).to include("me") + end + end + + describe "#exclude" do + it "adds the item to the list" do + subject.exclude("not me") + expect(subject.excludes).to include("not me") + end + end +end diff --git a/test/unit/plugins/pushes/ftp/push_test.rb b/test/unit/plugins/pushes/ftp/push_test.rb new file mode 100644 index 000000000..1f6773e24 --- /dev/null +++ b/test/unit/plugins/pushes/ftp/push_test.rb @@ -0,0 +1,297 @@ +require_relative "../../../base" +require "fake_ftp" + +require Vagrant.source_root.join("plugins/pushes/ftp/push") + +describe VagrantPlugins::FTPPush::Push do + include_context "unit" + + let(:env) { isolated_environment } + let(:config) do + double("config", + host: "127.0.0.1:51234", + username: "sethvargo", + password: "bacon", + passive: false, + secure: false, + destination: "/var/www/site", + ) + end + let(:ui) do + double("ui", + info: nil, + ) + end + + subject { described_class.new(env, config) } + + before do + allow(env).to receive(:root_path) + .and_return(File.expand_path("..", __FILE__)) + allow(env).to receive(:ui) + .and_return(ui) + end + + describe "#push" do + before(:all) do + @server = FakeFtp::Server.new(51234, 21213) + @server.start + + @dir = Dir.mktmpdir + + FileUtils.touch("#{@dir}/.hidden.rb") + FileUtils.touch("#{@dir}/application.rb") + FileUtils.touch("#{@dir}/config.rb") + FileUtils.touch("#{@dir}/Gemfile") + FileUtils.touch("#{@dir}/data.txt") + FileUtils.mkdir("#{@dir}/empty_folder") + end + + after(:all) do + FileUtils.rm_rf(@dir) + @server.stop + end + + let(:server) { @server } + + before do + allow(config).to receive(:dir) + .and_return(@dir) + + allow(config).to receive(:includes) + .and_return([]) + + allow(config).to receive(:excludes) + .and_return(%w(*.rb)) + end + + + it "pushes the files to the server" do + subject.push + expect(server.files).to eq(%w(Gemfile data.txt)) + end + end + + describe "#connect" do + before do + allow_any_instance_of(VagrantPlugins::FTPPush::FTPAdapter) + .to receive(:connect) + .and_yield(:ftp) + allow_any_instance_of(VagrantPlugins::FTPPush::SFTPAdapter) + .to receive(:connect) + .and_yield(:sftp) + end + + context "when secure is requested" do + before do + allow(config).to receive(:secure) + .and_return(true) + end + + it "yields a new SFTPAdapter" do + expect { |b| subject.connect(&b) }.to yield_with_args(:sftp) + end + end + + context "when secure is not requested" do + before do + allow(config).to receive(:secure) + .and_return(false) + end + + it "yields a new FTPAdapter" do + expect { |b| subject.connect(&b) }.to yield_with_args(:ftp) + end + end + end + + describe "#all_files" do + before(:all) do + @dir = Dir.mktmpdir + + FileUtils.touch("#{@dir}/.hidden.rb") + FileUtils.touch("#{@dir}/application.rb") + FileUtils.touch("#{@dir}/config.rb") + FileUtils.touch("#{@dir}/Gemfile") + FileUtils.mkdir("#{@dir}/empty_folder") + FileUtils.mkdir("#{@dir}/folder") + FileUtils.mkdir("#{@dir}/folder/.git") + FileUtils.touch("#{@dir}/folder/.git/config") + FileUtils.touch("#{@dir}/folder/server.rb") + end + + after(:all) do + FileUtils.rm_rf(@dir) + end + + let(:files) do + subject.all_files.map do |file| + file.sub("#{@dir}/", "") + end + end + + before do + allow(config).to receive(:dir) + .and_return(@dir) + + allow(config).to receive(:includes) + .and_return(%w(not_a_file.rb still_not_a_file.rb)) + + allow(config).to receive(:excludes) + .and_return(%w(*.rb)) + end + + it "returns the list of real files + includes, without excludes" do + expect(files).to eq(%w( + Gemfile + folder/.git/config + )) + end + end + + describe "includes_files" do + before(:all) do + @dir = Dir.mktmpdir + + FileUtils.touch("#{@dir}/.hidden.rb") + FileUtils.touch("#{@dir}/application.rb") + FileUtils.touch("#{@dir}/config.rb") + FileUtils.touch("#{@dir}/Gemfile") + FileUtils.mkdir("#{@dir}/folder") + FileUtils.mkdir("#{@dir}/folder/.git") + FileUtils.touch("#{@dir}/folder/.git/config") + FileUtils.touch("#{@dir}/folder/server.rb") + end + + after(:all) do + FileUtils.rm_rf(@dir) + end + + let(:files) do + subject.includes_files.map do |file| + file.sub("#{@dir}/", "") + end + end + + before do + allow(config).to receive(:dir) + .and_return(@dir) + end + + def set_includes(value) + allow(config).to receive(:includes) + .and_return(value) + end + + it "includes the file" do + set_includes(["Gemfile"]) + expect(files).to eq(%w( + Gemfile + )) + end + + it "includes the files that are subdirectories" do + set_includes(["folder"]) + expect(files).to eq(%w( + folder + folder/.git + folder/.git/config + folder/server.rb + )) + end + + it "includes files that match a pattern" do + set_includes(["*.rb"]) + expect(files).to eq(%w( + .hidden.rb + application.rb + config.rb + )) + end + end + + describe "#filter_excludes" do + let(:dir) { "/root/dir" } + + let(:list) do + %W( + #{dir}/.hidden.rb + #{dir}/application.rb + #{dir}/config.rb + #{dir}/Gemfile + #{dir}/folder + #{dir}/folder/.git + #{dir}/folder/.git/config + #{dir}/folder/server.rb + + /path/outside/you.rb + /path/outside/me.rb + /path/outside/folder/bacon.rb + ) + end + + before do + allow(config).to receive(:dir) + .and_return(dir) + end + + it "excludes files" do + subject.filter_excludes!(list, %w(*.rb)) + + expect(list).to eq(%W( + #{dir}/Gemfile + #{dir}/folder + #{dir}/folder/.git + #{dir}/folder/.git/config + )) + end + + it "excludes files in a directory" do + subject.filter_excludes!(list, %w(folder)) + + expect(list).to eq(%W( + #{dir}/.hidden.rb + #{dir}/application.rb + #{dir}/config.rb + #{dir}/Gemfile + + /path/outside/you.rb + /path/outside/me.rb + /path/outside/folder/bacon.rb + )) + end + + it "excludes specific files in a directory" do + subject.filter_excludes!(list, %w(/path/outside/folder/*.rb)) + + expect(list).to eq(%W( + #{dir}/.hidden.rb + #{dir}/application.rb + #{dir}/config.rb + #{dir}/Gemfile + #{dir}/folder + #{dir}/folder/.git + #{dir}/folder/.git/config + #{dir}/folder/server.rb + + /path/outside/you.rb + /path/outside/me.rb + )) + end + + it "excludes files outside the #dir" do + subject.filter_excludes!(list, %w(/path/outside)) + + expect(list).to eq(%W( + #{dir}/.hidden.rb + #{dir}/application.rb + #{dir}/config.rb + #{dir}/Gemfile + #{dir}/folder + #{dir}/folder/.git + #{dir}/folder/.git/config + #{dir}/folder/server.rb + )) + end + end +end diff --git a/test/unit/plugins/pushes/heroku/config_test.rb b/test/unit/plugins/pushes/heroku/config_test.rb new file mode 100644 index 000000000..e451ad152 --- /dev/null +++ b/test/unit/plugins/pushes/heroku/config_test.rb @@ -0,0 +1,99 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/pushes/heroku/config") + +describe VagrantPlugins::HerokuPush::Config do + include_context "unit" + + before(:all) do + I18n.load_path << Vagrant.source_root.join("plugins/pushes/heroku/locales/en.yml") + I18n.reload! + end + + subject { described_class.new } + + let(:machine) { double("machine") } + + describe "#app" do + it "defaults to nil" do + subject.finalize! + expect(subject.app).to be(nil) + end + end + + describe "#dir" do + it "defaults to ." do + subject.finalize! + expect(subject.dir).to eq(".") + end + end + + describe "#git_bin" do + it "defaults to git" do + subject.finalize! + expect(subject.git_bin).to eq("git") + end + end + + describe "#remote" do + it "defaults to git" do + subject.finalize! + expect(subject.remote).to eq("heroku") + end + end + + describe "#validate" do + before do + allow(machine).to receive(:env) + .and_return(double("env", + root_path: "", + )) + + subject.app = "bacon" + subject.dir = "." + subject.git_bin = "git" + subject.remote = "heroku" + end + + let(:result) { subject.validate(machine) } + let(:errors) { result["Heroku push"] } + + context "when the app is missing" do + it "does not return an error" do + subject.app = "" + subject.finalize! + expect(errors).to be_empty + end + end + + context "when the git_bin is missing" do + it "returns an error" do + subject.git_bin = "" + subject.finalize! + expect(errors).to include(I18n.t("heroku_push.errors.missing_attribute", + attribute: "git_bin", + )) + end + end + + context "when the remote is missing" do + it "returns an error" do + subject.remote = "" + subject.finalize! + expect(errors).to include(I18n.t("heroku_push.errors.missing_attribute", + attribute: "remote", + )) + end + end + + context "when the dir is missing" do + it "returns an error" do + subject.dir = "" + subject.finalize! + expect(errors).to include(I18n.t("heroku_push.errors.missing_attribute", + attribute: "dir", + )) + end + end + end +end diff --git a/test/unit/plugins/pushes/heroku/push_test.rb b/test/unit/plugins/pushes/heroku/push_test.rb new file mode 100644 index 000000000..c0337e41f --- /dev/null +++ b/test/unit/plugins/pushes/heroku/push_test.rb @@ -0,0 +1,324 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/pushes/heroku/push") + +describe VagrantPlugins::HerokuPush::Push do + include_context "unit" + + before(:all) do + I18n.load_path << Vagrant.source_root.join("plugins/pushes/heroku/locales/en.yml") + I18n.reload! + end + + let(:env) { isolated_environment } + let(:config) do + double("config", + app: "bacon", + dir: "lib", + git_bin: "git", + remote: "heroku", + ) + end + + subject { described_class.new(env, config) } + + describe "#push" do + let(:branch) { "master" } + + let(:root_path) { "/handy/dandy" } + let(:dir) { "#{root_path}/#{config.dir}" } + + before do + allow(subject).to receive(:git_branch) + .and_return(branch) + allow(subject).to receive(:verify_git_bin!) + allow(subject).to receive(:verify_git_repo!) + allow(subject).to receive(:has_git_remote?) + allow(subject).to receive(:add_heroku_git_remote) + allow(subject).to receive(:git_push_heroku) + allow(subject).to receive(:execute!) + + allow(env).to receive(:root_path) + .and_return(root_path) + end + + it "verifies the git bin is present" do + expect(subject).to receive(:verify_git_bin!) + .with(config.git_bin) + subject.push + end + + it "verifies the directory is a git repo" do + expect(subject).to receive(:verify_git_repo!) + .with(dir) + subject.push + end + + context "when the heroku remote exists" do + before do + allow(subject).to receive(:has_git_remote?) + .and_return(true) + end + + it "does not add the heroku remote" do + expect(subject).to_not receive(:add_heroku_git_remote) + subject.push + end + end + + context "when the heroku remote does not exist" do + before do + allow(subject).to receive(:has_git_remote?) + .and_return(false) + end + + it "adds the heroku remote" do + expect(subject).to receive(:add_heroku_git_remote) + .with(config.remote, config.app, dir) + subject.push + end + end + + it "pushes to heroku" do + expect(subject).to receive(:git_push_heroku) + .with(config.remote, branch, dir) + subject.push + end + end + + describe "#verify_git_bin!" do + context "when git does not exist" do + before do + allow(Vagrant::Util::Which).to receive(:which) + .with("git") + .and_return(nil) + end + + it "raises an exception" do + expect { + subject.verify_git_bin!("git") + } .to raise_error(VagrantPlugins::HerokuPush::Errors::GitNotFound) { |error| + expect(error.message).to eq(I18n.t("heroku_push.errors.git_not_found", + bin: "git", + )) + } + end + end + + context "when git exists" do + before do + allow(Vagrant::Util::Which).to receive(:which) + .with("git") + .and_return("git") + end + + it "does not raise an exception" do + expect { subject.verify_git_bin!("git") }.to_not raise_error + end + end + end + + describe "#verify_git_repo!" do + context "when the path is a git repo" do + before do + allow(File).to receive(:directory?) + .with("/repo/path/.git") + .and_return(false) + end + + it "raises an exception" do + expect { + subject.verify_git_repo!("/repo/path") + } .to raise_error(VagrantPlugins::HerokuPush::Errors::NotAGitRepo) { |error| + expect(error.message).to eq(I18n.t("heroku_push.errors.not_a_git_repo", + path: "/repo/path", + )) + } + end + end + + context "when the path is not a git repo" do + before do + allow(File).to receive(:directory?) + .with("/repo/path/.git") + .and_return(true) + end + + it "does not raise an exception" do + expect { subject.verify_git_repo!("/repo/path") }.to_not raise_error + end + end + end + + describe "#git_push_heroku" do + let(:dir) { "." } + + before { allow(subject).to receive(:execute!) } + + it "executes the proper command" do + expect(subject).to receive(:execute!) + .with("git", + "--git-dir", "#{dir}/.git", + "--work-tree", dir, + "push", "bacon", "hamlet:master", + ) + subject.git_push_heroku("bacon", "hamlet", dir) + end + end + + describe "#has_git_remote?" do + let(:dir) { "." } + + let(:process) do + double("process", + stdout: "origin\r\nbacon\nhello" + ) + end + + before do + allow(subject).to receive(:execute!) + .and_return(process) + end + + it "executes the proper command" do + expect(subject).to receive(:execute!) + .with("git", + "--git-dir", "#{dir}/.git", + "--work-tree", dir, + "remote", + ) + subject.has_git_remote?("bacon", dir) + end + + it "returns true when the remote exists" do + expect(subject.has_git_remote?("origin", dir)).to be(true) + expect(subject.has_git_remote?("bacon", dir)).to be(true) + expect(subject.has_git_remote?("hello", dir)).to be(true) + end + + it "returns false when the remote does not exist" do + expect(subject.has_git_remote?("nope", dir)).to be(false) + end + end + + describe "#add_heroku_git_remote" do + let(:dir) { "." } + + before do + allow(subject).to receive(:execute!) + allow(subject).to receive(:heroku_git_url) + .with("app") + .and_return("HEROKU_URL") + end + + it "executes the proper command" do + expect(subject).to receive(:execute!) + .with("git", + "--git-dir", "#{dir}/.git", + "--work-tree", dir, + "remote", "add", "bacon", "HEROKU_URL", + ) + subject.add_heroku_git_remote("bacon", "app", dir) + end + end + + describe "#interpret_app" do + it "returns the basename of the directory" do + expect(subject.interpret_app("/foo/bar/blitz")).to eq("blitz") + end + end + + describe "#heroku_git_url" do + it "returns the proper string" do + expect(subject.heroku_git_url("bacon")) + .to eq("git@heroku.com:bacon.git") + end + end + + describe "#git_dir" do + it "returns the .git directory for the path" do + expect(subject.git_dir("/path")).to eq("/path/.git") + end + end + + describe "#git_branch" do + let(:stdout) { "" } + let(:process) { double("process", stdout: stdout) } + + before do + allow(subject).to receive(:execute!) + .and_return(process) + end + + let(:branch) { subject.git_branch("/path") } + + context "when the branch is prefixed with a star" do + let(:stdout) { "*bacon" } + + it "returns the correct name" do + expect(branch).to eq("bacon") + end + end + + context "when the branch is prefixed with a star space" do + let(:stdout) { "* bacon" } + + it "returns the correct name" do + expect(branch).to eq("bacon") + end + end + + context "when the branch is not prefixed" do + let(:stdout) { "bacon" } + + it "returns the correct name" do + expect(branch).to eq("bacon") + end + end + end + + describe "#execute!" do + let(:exit_code) { 0 } + let(:stdout) { "This is the output" } + let(:stderr) { "This is the errput" } + + let(:process) do + double("process", + exit_code: exit_code, + stdout: stdout, + stderr: stderr, + ) + end + + before do + allow(Vagrant::Util::Subprocess).to receive(:execute) + .and_return(process) + end + + it "creates a subprocess" do + expect(Vagrant::Util::Subprocess).to receive(:execute) + expect { subject.execute! }.to_not raise_error + end + + it "returns the resulting process" do + expect(subject.execute!).to be(process) + end + + context "when the exit code is non-zero" do + let(:exit_code) { 1 } + + it "raises an exception" do + klass = VagrantPlugins::HerokuPush::Errors::CommandFailed + cmd = ["foo", "bar"] + + expect { subject.execute!(*cmd) }.to raise_error(klass) { |error| + expect(error.message).to eq(I18n.t("heroku_push.errors.command_failed", + cmd: cmd.join(" "), + stdout: stdout, + stderr: stderr, + )) + } + end + end + end +end diff --git a/test/unit/plugins/pushes/local-exec/config_test.rb b/test/unit/plugins/pushes/local-exec/config_test.rb new file mode 100644 index 000000000..045872d2f --- /dev/null +++ b/test/unit/plugins/pushes/local-exec/config_test.rb @@ -0,0 +1,85 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/pushes/local-exec/config") + +describe VagrantPlugins::LocalExecPush::Config do + include_context "unit" + + before(:all) do + I18n.load_path << Vagrant.source_root.join("plugins/pushes/local-exec/locales/en.yml") + I18n.reload! + end + + let(:machine) { double("machine") } + + describe "#script" do + it "defaults to nil" do + subject.finalize! + expect(subject.script).to be(nil) + end + end + + describe "#inline" do + it "defaults to nil" do + subject.finalize! + expect(subject.inline).to be(nil) + end + end + + describe "#validate" do + before do + allow(machine).to receive(:env) + .and_return(double("env", + root_path: "", + )) + subject.finalize! + end + + let(:result) { subject.validate(machine) } + let(:errors) { result["Local Exec push"] } + + context "when script is present" do + before { subject.script = "foo.sh" } + + context "when inline is present" do + before { subject.inline = "echo" } + + it "returns an error" do + expect(errors).to include( + I18n.t("local_exec_push.errors.cannot_specify_script_and_inline") + ) + end + end + + context "when inline is not present" do + before { subject.inline = "" } + + it "does not return an error" do + expect(errors).to be_empty + end + end + end + + context "when script is not present" do + before { subject.script = "" } + + context "when inline is present" do + before { subject.inline = "echo" } + + it "does not return an error" do + expect(errors).to be_empty + end + end + + context "when inline is not present" do + before { subject.inline = "" } + + it "returns an error" do + expect(errors).to include(I18n.t("local_exec_push.errors.missing_attribute", + attribute: "script", + )) + end + end + end + end +end diff --git a/test/unit/plugins/pushes/local-exec/push_test.rb b/test/unit/plugins/pushes/local-exec/push_test.rb new file mode 100644 index 000000000..1efdfa3b1 --- /dev/null +++ b/test/unit/plugins/pushes/local-exec/push_test.rb @@ -0,0 +1,101 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/pushes/local-exec/push") + +describe VagrantPlugins::LocalExecPush::Push do + include_context "unit" + + before(:all) do + I18n.load_path << Vagrant.source_root.join("plugins/pushes/local-exec/locales/en.yml") + I18n.reload! + end + + let(:env) { isolated_environment } + let(:config) do + double("config", + script: nil, + inline: nil, + ) + end + + subject { described_class.new(env, config) } + + before do + allow(env).to receive(:root_path) + .and_return(File.expand_path("..", __FILE__)) + end + + describe "#push" do + before do + allow(subject).to receive(:execute_inline!) + allow(subject).to receive(:execute_script!) + allow(subject).to receive(:execute!) + end + + context "when inline is given" do + before { allow(config).to receive(:inline).and_return("echo") } + + it "executes the inline script" do + expect(subject).to receive(:execute_inline!) + .with(config.inline) + subject.push + end + end + + context "when script is given" do + before { allow(config).to receive(:script).and_return("foo.sh") } + + it "executes the script" do + expect(subject).to receive(:execute_script!) + .with(config.script) + subject.push + end + end + end + + describe "#execute_inline!" do + before { allow(subject).to receive(:execute_script!) } + + it "writes the script to a tempfile" do + expect(Tempfile).to receive(:new).and_call_original + subject.execute_inline!("echo") + end + + it "executes the script" do + expect(subject).to receive(:execute_script!) + subject.execute_inline!("echo") + end + end + + describe "#execute_script!" do + before do + allow(subject).to receive(:execute!) + allow(FileUtils).to receive(:chmod) + end + + it "expands the path relative to the machine root" do + expect(subject).to receive(:execute!) + .with(File.expand_path("foo.sh", env.root_path)) + subject.execute_script!("./foo.sh") + end + + it "makes the file executable" do + expect(FileUtils).to receive(:chmod) + .with("+x", File.expand_path("foo.sh", env.root_path)) + subject.execute_script!("./foo.sh") + end + + it "calls execute!" do + expect(subject).to receive(:execute!) + .with(File.expand_path("foo.sh", env.root_path)) + subject.execute_script!("./foo.sh") + end + end + + describe "#execute!" do + it "safe execs" do + expect(Vagrant::Util::SafeExec).to receive(:exec) + expect { subject.execute! }.to_not raise_error + end + end +end diff --git a/test/unit/plugins/pushes/noop/config_test.rb b/test/unit/plugins/pushes/noop/config_test.rb new file mode 100644 index 000000000..05be6c3f8 --- /dev/null +++ b/test/unit/plugins/pushes/noop/config_test.rb @@ -0,0 +1,14 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/pushes/noop/config") + +describe VagrantPlugins::NoopDeploy::Config do + include_context "unit" + + subject { described_class.new } + + let(:machine) { double("machine") } + + describe "#validate" do + end +end diff --git a/test/unit/support/dummy_provider.rb b/test/unit/support/dummy_provider.rb index 4fba81c9a..c16865ae2 100644 --- a/test/unit/support/dummy_provider.rb +++ b/test/unit/support/dummy_provider.rb @@ -22,8 +22,13 @@ module VagrantTests end def state - state_id = :running - state_id = state_file.read.to_sym if state_file.file? + if !state_file.file? + new_state = @machine.id + new_state = Vagrant::MachineState::NOT_CREATED_ID if !new_state + self.state = new_state + end + + state_id = state_file.read.to_sym Vagrant::MachineState.new(state_id, state_id.to_s, state_id.to_s) end diff --git a/test/unit/support/shared/base_context.rb b/test/unit/support/shared/base_context.rb index 35c26788a..bd7f3f517 100644 --- a/test/unit/support/shared/base_context.rb +++ b/test/unit/support/shared/base_context.rb @@ -12,6 +12,9 @@ shared_context "unit" do # Create a thing to store our temporary files so that they aren't # unlinked right away. @_temp_files = [] + + # Roughly simulate the embedded Bundler availability + $vagrant_bundler_runtime = Object.new end after(:each) do @@ -83,6 +86,18 @@ shared_context "unit" do return Pathname.new(d) end + # Stub the given environment in ENV, without actually touching ENV. Keys and + # values are converted to strings because that's how the real ENV works. + def stub_env(hash) + allow(ENV).to receive(:[]).and_call_original + + hash.each do |key, value| + allow(ENV).to receive(:[]) + .with(key.to_s) + .and_return(value.to_s) + end + end + # This helper provides temporary environmental variable changes. def with_temp_env(environment) # Build up the new environment, preserving the old values so we diff --git a/test/unit/templates/guests/arch/network_dhcp_test.rb b/test/unit/templates/guests/arch/network_dhcp_test.rb new file mode 100644 index 000000000..10a3f2c7d --- /dev/null +++ b/test/unit/templates/guests/arch/network_dhcp_test.rb @@ -0,0 +1,19 @@ +require_relative "../../../base" + +require "vagrant/util/template_renderer" + +describe "templates/guests/arch/network_dhcp" do + let(:template) { "guests/arch/network_dhcp" } + + it "renders the template" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + device: "en0", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + Description='A basic dhcp ethernet connection' + Interface=en0 + Connection=ethernet + IP=dhcp + EOH + end +end diff --git a/test/unit/templates/guests/arch/network_static_test.rb b/test/unit/templates/guests/arch/network_static_test.rb new file mode 100644 index 000000000..375ec67c5 --- /dev/null +++ b/test/unit/templates/guests/arch/network_static_test.rb @@ -0,0 +1,37 @@ +require_relative "../../../base" + +require "vagrant/util/template_renderer" + +describe "templates/guests/arch/network_static" do + let(:template) { "guests/arch/network_static" } + + it "renders the template" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + device: "en0", + ip: "1.1.1.1", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + Connection=ethernet + Description='A basic static ethernet connection' + Interface=en0 + IP=static + Address=('1.1.1.1/24') + EOH + end + + it "includes the gateway" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + device: "en0", + ip: "1.1.1.1", + gateway: "1.2.3.4", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + Connection=ethernet + Description='A basic static ethernet connection' + Interface=en0 + IP=static + Address=('1.1.1.1/24') + Gateway='1.2.3.4' + EOH + end +end diff --git a/test/unit/templates/guests/debian/network_dhcp_test.rb b/test/unit/templates/guests/debian/network_dhcp_test.rb new file mode 100644 index 000000000..a5a0eba8d --- /dev/null +++ b/test/unit/templates/guests/debian/network_dhcp_test.rb @@ -0,0 +1,41 @@ +require_relative "../../../base" + +require "vagrant/util/template_renderer" + +describe "templates/guests/debian/network_dhcp" do + let(:template) { "guests/debian/network_dhcp" } + + it "renders the template" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + device: "en0", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + auto eth + iface eth inet dhcp + post-up route del default dev $IFACE || true + #VAGRANT-END + EOH + end + + context "when use_dhcp_assigned_default_route is set" do + it "renders the template" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + device: "en0", + use_dhcp_assigned_default_route: true, + }) + expect(result).to eq <<-EOH.gsub(/^ {8}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + auto eth + iface eth inet dhcp + # We need to disable eth0, see GH-2648 + post-up route del default dev eth0 || true + post-up dhclient $IFACE + pre-down route add default dev eth0 + #VAGRANT-END + EOH + end + end +end diff --git a/test/unit/templates/guests/debian/network_static_test.rb b/test/unit/templates/guests/debian/network_static_test.rb new file mode 100644 index 000000000..936a6a806 --- /dev/null +++ b/test/unit/templates/guests/debian/network_static_test.rb @@ -0,0 +1,43 @@ +require_relative "../../../base" + +require "vagrant/util/template_renderer" + +describe "templates/guests/debian/network_static" do + let(:template) { "guests/debian/network_static" } + + it "renders the template" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + device: "en0", + ip: "1.1.1.1", + netmask: "255.255.0.0", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + auto eth + iface eth inet static + address 1.1.1.1 + netmask 255.255.0.0 + #VAGRANT-END + EOH + end + + it "includes the gateway" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + device: "en0", + ip: "1.1.1.1", + netmask: "255.255.0.0", + gateway: "1.2.3.4", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + auto eth + iface eth inet static + address 1.1.1.1 + netmask 255.255.0.0 + gateway 1.2.3.4 + #VAGRANT-END + EOH + end +end diff --git a/test/unit/templates/guests/fedora/network_dhcp_test.rb b/test/unit/templates/guests/fedora/network_dhcp_test.rb new file mode 100644 index 000000000..b257aa76e --- /dev/null +++ b/test/unit/templates/guests/fedora/network_dhcp_test.rb @@ -0,0 +1,21 @@ +require_relative "../../../base" + +require "vagrant/util/template_renderer" + +describe "templates/guests/fedora/network_dhcp" do + let(:template) { "guests/fedora/network_dhcp" } + + it "renders the template" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + device: "en0", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + BOOTPROTO=dhcp + ONBOOT=yes + DEVICE=en0 + #VAGRANT-END + EOH + end +end diff --git a/test/unit/templates/guests/fedora/network_static_test.rb b/test/unit/templates/guests/fedora/network_static_test.rb new file mode 100644 index 000000000..f6e879fa2 --- /dev/null +++ b/test/unit/templates/guests/fedora/network_static_test.rb @@ -0,0 +1,71 @@ +require_relative "../../../base" + +require "vagrant/util/template_renderer" + +describe "templates/guests/fedora/network_static" do + let(:template) { "guests/fedora/network_static" } + + it "renders the template" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + device: "en0", + ip: "1.1.1.1", + netmask: "255.255.0.0", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + NM_CONTROLLED=no + BOOTPROTO=none + ONBOOT=yes + IPADDR=1.1.1.1 + NETMASK=255.255.0.0 + DEVICE=en0 + PEERDNS=no + #VAGRANT-END + EOH + end + + it "includes the gateway" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + device: "en0", + ip: "1.1.1.1", + netmask: "255.255.0.0", + gateway: "1.2.3.4", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + NM_CONTROLLED=no + BOOTPROTO=none + ONBOOT=yes + IPADDR=1.1.1.1 + NETMASK=255.255.0.0 + DEVICE=en0 + GATEWAY=1.2.3.4 + PEERDNS=no + #VAGRANT-END + EOH + end + + it "includes the mac_address" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + device: "en0", + ip: "1.1.1.1", + netmask: "255.255.0.0", + mac_address: "abcd1234", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + NM_CONTROLLED=no + BOOTPROTO=none + ONBOOT=yes + IPADDR=1.1.1.1 + NETMASK=255.255.0.0 + DEVICE=en0 + HWADDR=abcd1234 + PEERDNS=no + #VAGRANT-END + EOH + end +end diff --git a/test/unit/templates/guests/freebsd/network_dhcp_test.rb b/test/unit/templates/guests/freebsd/network_dhcp_test.rb new file mode 100644 index 000000000..322de3746 --- /dev/null +++ b/test/unit/templates/guests/freebsd/network_dhcp_test.rb @@ -0,0 +1,16 @@ +require_relative "../../../base" + +require "vagrant/util/template_renderer" + +describe "templates/guests/freebsd/network_dhcp" do + let(:template) { "guests/freebsd/network_dhcp" } + + it "renders the template" do + result = Vagrant::Util::TemplateRenderer.render(template, ifname: "vtneten0") + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + ifconfig_vtneten0="DHCP" + #VAGRANT-END + EOH + end +end diff --git a/test/unit/templates/guests/freebsd/network_static_test.rb b/test/unit/templates/guests/freebsd/network_static_test.rb new file mode 100644 index 000000000..ca9eb0153 --- /dev/null +++ b/test/unit/templates/guests/freebsd/network_static_test.rb @@ -0,0 +1,39 @@ +require_relative "../../../base" + +require "vagrant/util/template_renderer" + +describe "templates/guests/freebsd/network_static" do + let(:template) { "guests/freebsd/network_static" } + + it "renders the template" do + result = Vagrant::Util::TemplateRenderer.render(template, + ifname: "vtneten0", + options: { + ip: "1.1.1.1", + netmask: "255.255.0.0", + }, + ) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + ifconfig_vtneten0="inet 1.1.1.1 netmask 255.255.0.0" + #VAGRANT-END + EOH + end + + it "includes the gateway" do + result = Vagrant::Util::TemplateRenderer.render(template, + ifname: "vtneten0", + options: { + ip: "1.1.1.1", + netmask: "255.255.0.0", + gateway: "1.2.3.4", + }, + ) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + ifconfig_vtneten0="inet 1.1.1.1 netmask 255.255.0.0" + default_router="1.2.3.4" + #VAGRANT-END + EOH + end +end diff --git a/test/unit/templates/guests/funtoo/network_dhcp_test.rb b/test/unit/templates/guests/funtoo/network_dhcp_test.rb new file mode 100644 index 000000000..0e3cafb1c --- /dev/null +++ b/test/unit/templates/guests/funtoo/network_dhcp_test.rb @@ -0,0 +1,19 @@ +require_relative "../../../base" + +require "vagrant/util/template_renderer" + +describe "templates/guests/funtoo/network_dhcp" do + let(:template) { "guests/funtoo/network_dhcp" } + + it "renders the template" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + device: "en0", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + template='dhcp' + #VAGRANT-END + EOH + end +end diff --git a/test/unit/templates/guests/funtoo/network_static_test.rb b/test/unit/templates/guests/funtoo/network_static_test.rb new file mode 100644 index 000000000..5a14312d2 --- /dev/null +++ b/test/unit/templates/guests/funtoo/network_static_test.rb @@ -0,0 +1,141 @@ +require_relative "../../../base" + +require "vagrant/util/template_renderer" + +describe "templates/guests/funtoo/network_static" do + let(:template) { "guests/funtoo/network_static" } + + it "renders the template" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + device: "en0", + ip: "1.1.1.1", + netmask: "255.255.0.0", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + template='interface' + ipaddr='1.1.1.1/255.255.0.0' + #VAGRANT-END + EOH + end + + it "includes the gateway" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + device: "en0", + ip: "1.1.1.1", + netmask: "255.255.0.0", + gateway: "1.2.3.4", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + template='interface' + ipaddr='1.1.1.1/255.255.0.0' + gateway='1.2.3.4' + #VAGRANT-END + EOH + end + + it "includes the nameservers" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + device: "en0", + ip: "1.1.1.1", + netmask: "255.255.0.0", + nameservers: "ns1.company.com", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + template='interface' + ipaddr='1.1.1.1/255.255.0.0' + nameservers='ns1.company.com' + #VAGRANT-END + EOH + end + + it "includes the domain" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + device: "en0", + ip: "1.1.1.1", + netmask: "255.255.0.0", + domain: "company.com", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + template='interface' + ipaddr='1.1.1.1/255.255.0.0' + domain='company.com' + #VAGRANT-END + EOH + end + + it "includes the route" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + device: "en0", + ip: "1.1.1.1", + netmask: "255.255.0.0", + route: "5.6.7.8", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + template='interface' + ipaddr='1.1.1.1/255.255.0.0' + route='5.6.7.8' + #VAGRANT-END + EOH + end + + it "includes the gateway6" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + device: "en0", + ip: "1.1.1.1", + netmask: "255.255.0.0", + gateway6: "aaaa:0000", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + template='interface' + ipaddr='1.1.1.1/255.255.0.0' + gateway6='aaaa:0000' + #VAGRANT-END + EOH + end + + it "includes the route6" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + device: "en0", + ip: "1.1.1.1", + netmask: "255.255.0.0", + route6: "bbbb:1111", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + template='interface' + ipaddr='1.1.1.1/255.255.0.0' + route6='bbbb:1111' + #VAGRANT-END + EOH + end + + it "includes the mtu" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + device: "en0", + ip: "1.1.1.1", + netmask: "255.255.0.0", + mtu: "1", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + template='interface' + ipaddr='1.1.1.1/255.255.0.0' + mtu='1' + #VAGRANT-END + EOH + end +end diff --git a/test/unit/templates/guests/gentoo/network_dhcp_test.rb b/test/unit/templates/guests/gentoo/network_dhcp_test.rb new file mode 100644 index 000000000..7885c2acc --- /dev/null +++ b/test/unit/templates/guests/gentoo/network_dhcp_test.rb @@ -0,0 +1,19 @@ +require_relative "../../../base" + +require "vagrant/util/template_renderer" + +describe "templates/guests/gentoo/network_dhcp" do + let(:template) { "guests/gentoo/network_dhcp" } + + it "renders the template" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + interface: "en0", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + config_ethen0="dhcp" + #VAGRANT-END + EOH + end +end diff --git a/test/unit/templates/guests/gentoo/network_static_test.rb b/test/unit/templates/guests/gentoo/network_static_test.rb new file mode 100644 index 000000000..e1eb8ce1a --- /dev/null +++ b/test/unit/templates/guests/gentoo/network_static_test.rb @@ -0,0 +1,37 @@ +require_relative "../../../base" + +require "vagrant/util/template_renderer" + +describe "templates/guests/gentoo/network_static" do + let(:template) { "guests/gentoo/network_static" } + + it "renders the template" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + interface: "en0", + ip: "1.1.1.1", + netmask: "255.255.0.0", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + config_ethen0=("1.1.1.1 netmask 255.255.0.0") + #VAGRANT-END + EOH + end + + it "includes the gateway" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + interface: "en0", + ip: "1.1.1.1", + netmask: "255.255.0.0", + gateway: "1.2.3.4", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + config_ethen0=("1.1.1.1 netmask 255.255.0.0") + gateways_ethen0="1.2.3.4" + #VAGRANT-END + EOH + end +end diff --git a/test/unit/templates/guests/netbsd/network_dhcp_test.rb b/test/unit/templates/guests/netbsd/network_dhcp_test.rb new file mode 100644 index 000000000..c2f68d7ba --- /dev/null +++ b/test/unit/templates/guests/netbsd/network_dhcp_test.rb @@ -0,0 +1,18 @@ +require_relative "../../../base" + +require "vagrant/util/template_renderer" + +describe "templates/guests/netbsd/network_dhcp" do + let(:template) { "guests/netbsd/network_dhcp" } + + it "renders the template" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + interface: "en0", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + ifconfig_wmen0=dhcp + #VAGRANT-END + EOH + end +end diff --git a/test/unit/templates/guests/netbsd/network_static_test.rb b/test/unit/templates/guests/netbsd/network_static_test.rb new file mode 100644 index 000000000..210dc4d30 --- /dev/null +++ b/test/unit/templates/guests/netbsd/network_static_test.rb @@ -0,0 +1,20 @@ +require_relative "../../../base" + +require "vagrant/util/template_renderer" + +describe "templates/guests/netbsd/network_static" do + let(:template) { "guests/netbsd/network_static" } + + it "renders the template" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + interface: "en0", + ip: "1.1.1.1", + netmask: "255.255.0.0", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + ifconfig_wmen0="media autoselect up;inet 1.1.1.1 netmask 255.255.0.0" + #VAGRANT-END + EOH + end +end diff --git a/test/unit/templates/guests/redhat/network_dhcp_test.rb b/test/unit/templates/guests/redhat/network_dhcp_test.rb new file mode 100644 index 000000000..280678a2f --- /dev/null +++ b/test/unit/templates/guests/redhat/network_dhcp_test.rb @@ -0,0 +1,21 @@ +require_relative "../../../base" + +require "vagrant/util/template_renderer" + +describe "templates/guests/redhat/network_dhcp" do + let(:template) { "guests/redhat/network_dhcp" } + + it "renders the template" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + interface: "en0", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + BOOTPROTO=dhcp + ONBOOT=yes + DEVICE=ethen0 + #VAGRANT-END + EOH + end +end diff --git a/test/unit/templates/guests/redhat/network_static_test.rb b/test/unit/templates/guests/redhat/network_static_test.rb new file mode 100644 index 000000000..443ec2d56 --- /dev/null +++ b/test/unit/templates/guests/redhat/network_static_test.rb @@ -0,0 +1,49 @@ +require_relative "../../../base" + +require "vagrant/util/template_renderer" + +describe "templates/guests/redhat/network_static" do + let(:template) { "guests/redhat/network_static" } + + it "renders the template" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + interface: "en0", + ip: "1.1.1.1", + netmask: "255.255.0.0", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + NM_CONTROLLED=no + BOOTPROTO=none + ONBOOT=yes + IPADDR=1.1.1.1 + NETMASK=255.255.0.0 + DEVICE=ethen0 + PEERDNS=no + #VAGRANT-END + EOH + end + + it "includes the gateway" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + interface: "en0", + ip: "1.1.1.1", + gateway: "1.2.3.4", + netmask: "255.255.0.0", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + NM_CONTROLLED=no + BOOTPROTO=none + ONBOOT=yes + IPADDR=1.1.1.1 + NETMASK=255.255.0.0 + DEVICE=ethen0 + GATEWAY=1.2.3.4 + PEERDNS=no + #VAGRANT-END + EOH + end +end diff --git a/test/unit/templates/guests/suse/network_dhcp_test.rb b/test/unit/templates/guests/suse/network_dhcp_test.rb new file mode 100644 index 000000000..82595db47 --- /dev/null +++ b/test/unit/templates/guests/suse/network_dhcp_test.rb @@ -0,0 +1,21 @@ +require_relative "../../../base" + +require "vagrant/util/template_renderer" + +describe "templates/guests/suse/network_dhcp" do + let(:template) { "guests/suse/network_dhcp" } + + it "renders the template" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + interface: "en0", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + BOOTPROTO=dhcp + ONBOOT=yes + DEVICE=ethen0 + #VAGRANT-END + EOH + end +end diff --git a/test/unit/templates/guests/suse/network_static_test.rb b/test/unit/templates/guests/suse/network_static_test.rb new file mode 100644 index 000000000..09117268d --- /dev/null +++ b/test/unit/templates/guests/suse/network_static_test.rb @@ -0,0 +1,49 @@ +require_relative "../../../base" + +require "vagrant/util/template_renderer" + +describe "templates/guests/suse/network_static" do + let(:template) { "guests/suse/network_static" } + + it "renders the template" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + interface: "en0", + ip: "1.1.1.1", + netmask: "255.255.0.0", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + BOOTPROTO='static' + IPADDR='1.1.1.1' + NETMASK='255.255.0.0' + DEVICE='ethen0' + PEERDNS='no' + STARTMODE='auto' + USERCONTROL='no' + #VAGRANT-END + EOH + end + + it "includes the gateway" do + result = Vagrant::Util::TemplateRenderer.render(template, options: { + interface: "en0", + ip: "1.1.1.1", + gateway: "1.2.3.4", + netmask: "255.255.0.0", + }) + expect(result).to eq <<-EOH.gsub(/^ {6}/, "") + #VAGRANT-BEGIN + # The contents below are automatically generated by Vagrant. Do not modify. + BOOTPROTO='static' + IPADDR='1.1.1.1' + NETMASK='255.255.0.0' + DEVICE='ethen0' + GATEWAY='1.2.3.4' + PEERDNS='no' + STARTMODE='auto' + USERCONTROL='no' + #VAGRANT-END + EOH + end +end diff --git a/test/unit/vagrant/action/builtin/box_add_test.rb b/test/unit/vagrant/action/builtin/box_add_test.rb index 03e064920..ad3a56dfc 100644 --- a/test/unit/vagrant/action/builtin/box_add_test.rb +++ b/test/unit/vagrant/action/builtin/box_add_test.rb @@ -34,12 +34,14 @@ describe Vagrant::Action::Builtin::BoxAdd do FileChecksum.new(path, Digest::SHA1).checksum end - def with_web_server(path) + def with_web_server(path, **opts) tf = Tempfile.new("vagrant") tf.close + opts[:json_type] ||= "application/json" + mime_types = WEBrick::HTTPUtils::DefaultMimeTypes - mime_types.store "json", "application/json" + mime_types.store "json", opts[:json_type] port = 3838 server = WEBrick::HTTPServer.new( @@ -258,6 +260,51 @@ describe Vagrant::Action::Builtin::BoxAdd do end end + it "adds from HTTP URL with complex JSON mime type" do + box_path = iso_env.box2_file(:virtualbox) + tf = Tempfile.new(["vagrant", ".json"]).tap do |f| + f.write(<<-RAW) + { + "name": "foo/bar", + "versions": [ + { + "version": "0.5" + }, + { + "version": "0.7", + "providers": [ + { + "name": "virtualbox", + "url": "#{box_path}" + } + ] + } + ] + } + RAW + f.close + end + + opts = { json_type: "application/json; charset=utf-8" } + + md_path = Pathname.new(tf.path) + with_web_server(md_path, **opts) do |port| + env[:box_url] = "http://127.0.0.1:#{port}/#{md_path.basename}" + + expect(box_collection).to receive(:add).with { |path, name, version, **opts| + expect(name).to eq("foo/bar") + expect(version).to eq("0.7") + expect(checksum(path)).to eq(checksum(box_path)) + expect(opts[:metadata_url]).to eq(env[:box_url]) + true + }.and_return(box) + + expect(app).to receive(:call).with(env) + + subject.call(env) + end + end + it "adds from shorthand path" do box_path = iso_env.box2_file(:virtualbox) td = Pathname.new(Dir.mktmpdir) diff --git a/test/unit/vagrant/action/builtin/handle_box_test.rb b/test/unit/vagrant/action/builtin/handle_box_test.rb index 6b7d85818..17f32563f 100644 --- a/test/unit/vagrant/action/builtin/handle_box_test.rb +++ b/test/unit/vagrant/action/builtin/handle_box_test.rb @@ -106,4 +106,31 @@ describe Vagrant::Action::Builtin::HandleBox do subject.call(env) end end + + context "with a box with a checksum set" do + before do + machine.stub(box: nil) + + machine.config.vm.box = "foo" + machine.config.vm.box_url = "bar" + machine.config.vm.box_download_checksum_type = "sha256" + machine.config.vm.box_download_checksum = "1f42ac2decf0169c4af02b2d8c77143ce35f7ba87d5d844e19bf7cbb34fbe74e" + end + + it "adds a box that doesn't exist and maps checksum options correctly" do + expect(action_runner).to receive(:run).with { |action, opts| + expect(opts[:box_name]).to eq(machine.config.vm.box) + expect(opts[:box_url]).to eq(machine.config.vm.box_url) + expect(opts[:box_provider]).to eq(:dummy) + expect(opts[:box_version]).to eq(machine.config.vm.box_version) + expect(opts[:box_checksum_type]).to eq(machine.config.vm.box_download_checksum_type) + expect(opts[:box_checksum]).to eq(machine.config.vm.box_download_checksum) + true + } + + expect(app).to receive(:call).with(env) + + subject.call(env) + end + end end diff --git a/test/unit/vagrant/action/builtin/mixin_synced_folders_test.rb b/test/unit/vagrant/action/builtin/mixin_synced_folders_test.rb index 9ab2bf637..264e158d2 100644 --- a/test/unit/vagrant/action/builtin/mixin_synced_folders_test.rb +++ b/test/unit/vagrant/action/builtin/mixin_synced_folders_test.rb @@ -28,19 +28,47 @@ describe Vagrant::Action::Builtin::MixinSyncedFolders do end end - let(:vm_config) { double("machine_vm_config") } + let(:vm_config) { double("machine_vm_config", :allowed_synced_folder_types => nil) } describe "default_synced_folder_type" do it "returns the usable implementation" do plugins = { "bad" => [impl(false, "bad"), 0], - "nope" => [impl(true, "nope"), 1], - "good" => [impl(true, "good"), 5], + "good" => [impl(true, "good"), 1], + "best" => [impl(true, "best"), 5], + } + + result = subject.default_synced_folder_type(machine, plugins) + expect(result).to eq("best") + end + + it "filters based on allowed_synced_folder_types" do + expect(vm_config).to receive(:allowed_synced_folder_types).and_return(["bad", "good"]) + plugins = { + "bad" => [impl(false, "bad"), 0], + "good" => [impl(true, "good"), 1], + "best" => [impl(true, "best"), 5], } result = subject.default_synced_folder_type(machine, plugins) expect(result).to eq("good") end + + it "reprioritizes based on allowed_synced_folder_types" do + plugins = { + "bad" => [impl(false, "bad"), 0], + "good" => [impl(true, "good"), 1], + "same" => [impl(true, "same"), 1], + } + + expect(vm_config).to receive(:allowed_synced_folder_types).and_return(["good", "same"]) + result = subject.default_synced_folder_type(machine, plugins) + expect(result).to eq("good") + + expect(vm_config).to receive(:allowed_synced_folder_types).and_return(["same", "good"]) + result = subject.default_synced_folder_type(machine, plugins) + expect(result).to eq("same") + end end describe "impl_opts" do diff --git a/test/unit/vagrant/action/builtin/ssh_exec_test.rb b/test/unit/vagrant/action/builtin/ssh_exec_test.rb index 17f355497..94b42b99c 100644 --- a/test/unit/vagrant/action/builtin/ssh_exec_test.rb +++ b/test/unit/vagrant/action/builtin/ssh_exec_test.rb @@ -1,7 +1,5 @@ require File.expand_path("../../../../base", __FILE__) -require "vagrant/util/ssh" - describe Vagrant::Action::Builtin::SSHExec do let(:app) { lambda { |env| } } let(:env) { { machine: machine } } @@ -16,7 +14,6 @@ describe Vagrant::Action::Builtin::SSHExec do before(:each) do # Stub the methods so that even if we test incorrectly, no side # effects actually happen. - allow(ssh_klass).to receive(:check_key_permissions) allow(ssh_klass).to receive(:exec) end @@ -29,23 +26,6 @@ describe Vagrant::Action::Builtin::SSHExec do to raise_error(Vagrant::Errors::SSHNotReady) end - it "should check key permissions then exec" do - key_path = "/foo" - machine_ssh_info[:private_key_path] = [key_path] - - expect(ssh_klass).to receive(:check_key_permissions). - with(Pathname.new(key_path)). - once. - ordered - - expect(ssh_klass).to receive(:exec). - with(machine_ssh_info, nil). - once. - ordered - - described_class.new(app, env).call(env) - end - it "should exec with the SSH info in the env if given" do ssh_info = { foo: :bar } diff --git a/test/unit/vagrant/action/warden_test.rb b/test/unit/vagrant/action/warden_test.rb index b9758be7a..9c668e371 100644 --- a/test/unit/vagrant/action/warden_test.rb +++ b/test/unit/vagrant/action/warden_test.rb @@ -87,6 +87,6 @@ describe Vagrant::Action::Warden do expect { instance.call(data) }.to raise_error(SystemExit) # The recover should not have been called - expect(data.has_key?(:recover)).not_to be + expect(data.key?(:recover)).not_to be end end diff --git a/test/unit/vagrant/environment_test.rb b/test/unit/vagrant/environment_test.rb index a447ea9f1..c5cf8370d 100644 --- a/test/unit/vagrant/environment_test.rb +++ b/test/unit/vagrant/environment_test.rb @@ -253,7 +253,7 @@ describe Vagrant::Environment do describe "#machine" do # A helper to register a provider for use in tests. def register_provider(name, config_class=nil, options=nil) - provider_cls = Class.new(Vagrant.plugin("2", :provider)) + provider_cls = Class.new(VagrantTests::DummyProvider) register_plugin("2") do |p| p.provider(name, options) { provider_cls } @@ -798,13 +798,60 @@ VF end end - it "is VirtualBox if nothing else is usable" do + it "raise an error if nothing else is usable" do plugin_providers[:foo] = [provider_usable_class(false), { priority: 5 }] plugin_providers[:bar] = [provider_usable_class(false), { priority: 5 }] plugin_providers[:baz] = [provider_usable_class(false), { priority: 5 }] with_temp_env("VAGRANT_DEFAULT_PROVIDER" => nil) do - expect(subject.default_provider).to eq(:virtualbox) + expect { subject.default_provider }.to raise_error( + Vagrant::Errors::NoDefaultProvider) + end + end + + it "is the provider in the Vagrantfile that is usable" do + subject.vagrantfile.config.vm.provider "foo" + subject.vagrantfile.config.vm.provider "bar" + subject.vagrantfile.config.vm.finalize! + + plugin_providers[:foo] = [provider_usable_class(true), { priority: 5 }] + plugin_providers[:bar] = [provider_usable_class(true), { priority: 7 }] + plugin_providers[:baz] = [provider_usable_class(true), { priority: 2 }] + plugin_providers[:boom] = [provider_usable_class(true), { priority: 3 }] + + with_temp_env("VAGRANT_DEFAULT_PROVIDER" => nil) do + expect(subject.default_provider).to eq(:foo) + end + end + + it "is the highest usable provider outside the Vagrantfile" do + subject.vagrantfile.config.vm.provider "foo" + subject.vagrantfile.config.vm.finalize! + + plugin_providers[:foo] = [provider_usable_class(false), { priority: 5 }] + plugin_providers[:bar] = [provider_usable_class(true), { priority: 7 }] + plugin_providers[:baz] = [provider_usable_class(true), { priority: 2 }] + plugin_providers[:boom] = [provider_usable_class(true), { priority: 3 }] + + with_temp_env("VAGRANT_DEFAULT_PROVIDER" => nil) do + expect(subject.default_provider).to eq(:bar) + end + end + + it "is the provider in the Vagrantfile that is usable for a machine" do + subject.vagrantfile.config.vm.provider "foo" + subject.vagrantfile.config.vm.define "sub" do |v| + v.vm.provider "bar" + end + subject.vagrantfile.config.vm.finalize! + + plugin_providers[:foo] = [provider_usable_class(true), { priority: 5 }] + plugin_providers[:bar] = [provider_usable_class(true), { priority: 7 }] + plugin_providers[:baz] = [provider_usable_class(true), { priority: 2 }] + plugin_providers[:boom] = [provider_usable_class(true), { priority: 3 }] + + with_temp_env("VAGRANT_DEFAULT_PROVIDER" => nil) do + expect(subject.default_provider(machine: :sub)).to eq(:bar) end end end @@ -921,6 +968,76 @@ VF end end + describe "#pushes" do + it "returns the pushes from the Vagrantfile config" do + environment = isolated_environment do |env| + env.vagrantfile(<<-VF.gsub(/^ {10}/, '')) + Vagrant.configure("2") do |config| + config.push.define "noop" + end + VF + end + + env = environment.create_vagrant_env + expect(env.pushes).to eq([:noop]) + end + end + + describe "#push" do + let(:push_class) do + Class.new(Vagrant.plugin("2", :push)) do + def self.pushed? + !!class_variable_get(:@@pushed) + end + + def push + self.class.class_variable_set(:@@pushed, true) + end + end + end + + it "raises an exception when the push does not exist" do + expect { instance.push("lolwatbacon") } + .to raise_error(Vagrant::Errors::PushStrategyNotDefined) + end + + it "raises an exception if the strategy does not exist" do + environment = isolated_environment do |env| + env.vagrantfile(<<-VF.gsub(/^ {10}/, '')) + Vagrant.configure("2") do |config| + config.push.define "lolwatbacon" + end + VF + end + + env = environment.create_vagrant_env + expect { env.push("lolwatbacon") } + .to raise_error(Vagrant::Errors::PushStrategyNotLoaded) + end + + it "executes the push action" do + register_plugin("2") do |plugin| + plugin.name "foo" + + plugin.push(:foo) do + push_class + end + end + + environment = isolated_environment do |env| + env.vagrantfile(<<-VF.gsub(/^ {10}/, '')) + Vagrant.configure("2") do |config| + config.push.define "foo" + end + VF + end + + env = environment.create_vagrant_env + env.push("foo") + expect(push_class.pushed?).to be_true + end + end + describe "#hook" do it "should call the action runner with the proper hook" do hook_name = :foo diff --git a/test/unit/vagrant/machine_test.rb b/test/unit/vagrant/machine_test.rb index d8e55e346..4064fa1d5 100644 --- a/test/unit/vagrant/machine_test.rb +++ b/test/unit/vagrant/machine_test.rb @@ -7,11 +7,7 @@ describe Vagrant::Machine do include_context "unit" let(:name) { "foo" } - let(:provider) do - double("provider").tap do |obj| - obj.stub(_initialize: nil) - end - end + let(:provider) { new_provider_mock } let(:provider_cls) do obj = double("provider_cls") obj.stub(new: provider) @@ -46,6 +42,15 @@ describe Vagrant::Machine do subject { instance } + def new_provider_mock + double("provider").tap do |obj| + obj.stub(_initialize: nil) + obj.stub(machine_id_changed: nil) + allow(obj).to receive(:state).and_return(Vagrant::MachineState.new( + :created, "", "")) + end + end + # Returns a new instance with the test data def new_instance described_class.new(name, provider_name, provider_cls, provider_config, @@ -54,6 +59,17 @@ describe Vagrant::Machine do end describe "initialization" do + it "should set the ID to nil if the state is not created" do + subject.id = "foo" + + allow(provider).to receive(:state).and_return(Vagrant::MachineState.new( + Vagrant::MachineState::NOT_CREATED_ID, "short", "long")) + + subject = new_instance + expect(subject.state.id).to eq(Vagrant::MachineState::NOT_CREATED_ID) + expect(subject.id).to be_nil + end + describe "communicator loading" do it "doesn't eager load SSH" do config.vm.communicator = :ssh @@ -86,8 +102,7 @@ describe Vagrant::Machine do received_machine = nil if !instance - instance = double("instance") - instance.stub(_initialize: nil) + instance = new_provider_mock end provider_cls = double("provider_cls") @@ -166,7 +181,7 @@ describe Vagrant::Machine do end it "should initialize the capabilities" do - instance = double("instance") + instance = new_provider_mock expect(instance).to receive(:_initialize).with { |p, m| expect(p).to eq(provider_name) expect(m.name).to eq(name) @@ -248,6 +263,17 @@ describe Vagrant::Machine do expect(foo).to eq(:bar) end + it "should pass any extra options to the environment as strings" do + action_name = :up + foo = nil + callable = lambda { |env| foo = env["foo"] } + + allow(provider).to receive(:action).with(action_name).and_return(callable) + instance.action(:up, "foo" => :bar) + + expect(foo).to eq(:bar) + end + it "should return the environment as a result" do action_name = :up callable = lambda { |env| env[:result] = "FOO" } @@ -398,6 +424,21 @@ describe Vagrant::Machine do third = new_instance expect(third.id).to be_nil end + + it "should set the UID that created the machine" do + instance.id = "foo" + + second = new_instance + expect(second.uid).to eq(Process.uid.to_s) + end + + it "should delete the UID when the id is nil" do + instance.id = "foo" + instance.id = nil + + second = new_instance + expect(second.uid).to be_nil + end end describe "#index_uuid" do @@ -410,6 +451,10 @@ describe Vagrant::Machine do end it "is set one when setting an ID" do + # Stub the message we want + allow(provider).to receive(:state).and_return(Vagrant::MachineState.new( + :preparing, "preparing", "preparing")) + # Setup the box information box = double("box") box.stub(name: "foo") @@ -451,11 +496,12 @@ describe Vagrant::Machine do describe "#reload" do before do allow(provider).to receive(:machine_id_changed) - subject.id = "foo" end it "should read the ID" do + expect(provider).to_not receive(:machine_id_changed) + subject.reload expect(subject.id).to eq("foo") @@ -464,13 +510,16 @@ describe Vagrant::Machine do it "should read the updated ID" do new_instance.id = "bar" + expect(provider).to receive(:machine_id_changed) + subject.reload expect(subject.id).to eq("bar") end end - describe "ssh info" do + describe "#ssh_info" do + describe "with the provider returning nil" do it "should return nil if the provider returns nil" do expect(provider).to receive(:ssh_info).and_return(nil) @@ -480,9 +529,13 @@ describe Vagrant::Machine do describe "with the provider returning data" do let(:provider_ssh_info) { {} } + let(:ssh_klass) { Vagrant::Util::SSH } before(:each) do allow(provider).to receive(:ssh_info).and_return(provider_ssh_info) + # Stub the check_key_permissions method so that even if we test incorrectly, + # no side effects actually happen. + allow(ssh_klass).to receive(:check_key_permissions) end [:host, :port, :username].each do |type| @@ -554,6 +607,30 @@ describe Vagrant::Machine do ]) end + it "should check and try to fix the permissions of the default private key file" do + provider_ssh_info[:private_key_path] = nil + instance.config.ssh.private_key_path = nil + + expect(ssh_klass).to receive(:check_key_permissions).once.with(Pathname.new(instance.env.default_private_key_path.to_s)) + instance.ssh_info + end + + it "should check and try to fix the permissions of given private key files" do + provider_ssh_info[:private_key_path] = nil + # Use __FILE__ to provide an existing file + instance.config.ssh.private_key_path = [File.expand_path(__FILE__), File.expand_path(__FILE__)] + + expect(ssh_klass).to receive(:check_key_permissions).twice.with(Pathname.new(File.expand_path(__FILE__))) + instance.ssh_info + end + + it "should not check the permissions of a private key file that does not exist" do + provider_ssh_info[:private_key_path] = "/foo" + + expect(ssh_klass).to_not receive(:check_key_permissions) + instance.ssh_info + end + context "expanding path relative to the root path" do it "should with the provider key path" do provider_ssh_info[:private_key_path] = "~/foo" @@ -605,6 +682,17 @@ describe Vagrant::Machine do expect(instance.ssh_info[:password]).to eql("") end + it "should return the private key in the Vagrantfile if the data dir exists" do + provider_ssh_info[:private_key_path] = nil + instance.config.ssh.private_key_path = "/foo" + + instance.data_dir.join("private_key").open("w+") do |f| + f.write("hey") + end + + expect(instance.ssh_info[:private_key_path]).to eql(["/foo"]) + end + context "with no data dir" do let(:base) { true } let(:data_dir) { nil } @@ -625,7 +713,7 @@ describe Vagrant::Machine do it "should query state from the provider" do state = Vagrant::MachineState.new(:id, "short", "long") - expect(provider).to receive(:state).and_return(state) + allow(provider).to receive(:state).and_return(state) expect(instance.state.id).to eq(:id) end @@ -649,18 +737,6 @@ describe Vagrant::Machine do expect(entry.state).to eq("short") env.machine_index.release(entry) end - - it "should set the ID to nil if the state is not created" do - state = Vagrant::MachineState.new( - Vagrant::MachineState::NOT_CREATED_ID, "short", "long") - - allow(provider).to receive(:machine_id_changed) - subject.id = "foo" - - expect(provider).to receive(:state).and_return(state) - expect(subject.state.id).to eq(Vagrant::MachineState::NOT_CREATED_ID) - expect(subject.id).to be_nil - end end describe "#with_ui" do diff --git a/test/unit/vagrant/plugin/v2/command_test.rb b/test/unit/vagrant/plugin/v2/command_test.rb index a111e9d5f..7b42f2345 100644 --- a/test/unit/vagrant/plugin/v2/command_test.rb +++ b/test/unit/vagrant/plugin/v2/command_test.rb @@ -45,6 +45,17 @@ describe Vagrant::Plugin::V2::Command do expect { instance.parse_options(OptionParser.new) }. to raise_error(Vagrant::Errors::CLIInvalidOptions) end + + it "raises an error if options without a value are given" do + opts = OptionParser.new do |o| + o.on("--provision-with x,y,z", Array, "Example") { |f| } + end + + + instance = klass.new(["--provision-with"], nil) + expect { instance.parse_options(opts) }. + to raise_error(Vagrant::Errors::CLIInvalidOptions) + end end describe "target VMs" do @@ -73,14 +84,16 @@ describe Vagrant::Plugin::V2::Command do to raise_error(Vagrant::Errors::NoEnvironmentError) end - it "should yield every VM in order is no name is given" do + it "should yield every VM in order if no name is given" do foo_vm = double("foo") foo_vm.stub(name: "foo", provider: :foobarbaz) foo_vm.stub(ui: Vagrant::UI::Silent.new) + foo_vm.stub(state: nil) bar_vm = double("bar") bar_vm.stub(name: "bar", provider: :foobarbaz) bar_vm.stub(ui: Vagrant::UI::Silent.new) + bar_vm.stub(state: nil) environment.stub(machine_names: [:foo, :bar]) allow(environment).to receive(:machine).with(:foo, environment.default_provider).and_return(foo_vm) @@ -106,6 +119,7 @@ describe Vagrant::Plugin::V2::Command do foo_vm = double("foo") foo_vm.stub(name: "foo", provider: :foobarbaz) foo_vm.stub(ui: Vagrant::UI::Silent.new) + foo_vm.stub(state: nil) allow(environment).to receive(:machine).with(:foo, environment.default_provider).and_return(foo_vm) @@ -114,12 +128,26 @@ describe Vagrant::Plugin::V2::Command do expect(vms).to eq([foo_vm]) end + it "calls state after yielding the vm to update the machine index" do + foo_vm = double("foo") + foo_vm.stub(name: "foo", provider: :foobarbaz) + foo_vm.stub(ui: Vagrant::UI::Silent.new) + foo_vm.stub(state: nil) + + allow(environment).to receive(:machine).with(:foo, environment.default_provider).and_return(foo_vm) + + vms = [] + expect(foo_vm).to receive(:state) + instance.with_target_vms("foo") { |vm| vms << vm } + end + it "yields the given VM with proper provider if given" do foo_vm = double("foo") provider = :foobarbaz foo_vm.stub(name: "foo", provider: provider) foo_vm.stub(ui: Vagrant::UI::Silent.new) + foo_vm.stub(state: nil) allow(environment).to receive(:machine).with(:foo, provider).and_return(foo_vm) vms = [] @@ -144,6 +172,7 @@ describe Vagrant::Plugin::V2::Command do allow(environment).to receive(:machine).with(name, provider).and_return(vmware_vm) vmware_vm.stub(name: name, provider: provider) vmware_vm.stub(ui: Vagrant::UI::Silent.new) + vmware_vm.stub(state: nil) vms = [] instance.with_target_vms(name.to_s) { |vm| vms << vm } @@ -158,6 +187,7 @@ describe Vagrant::Plugin::V2::Command do environment.stub(active_machines: [[name, provider]]) allow(environment).to receive(:machine).with(name, provider).and_return(vmware_vm) vmware_vm.stub(name: name, provider: provider, ui: Vagrant::UI::Silent.new) + vmware_vm.stub(state: nil) vms = [] instance.with_target_vms(name.to_s, provider: provider) { |vm| vms << vm } @@ -171,6 +201,7 @@ describe Vagrant::Plugin::V2::Command do allow(environment).to receive(:machine).with(name, environment.default_provider).and_return(machine) machine.stub(name: name, provider: environment.default_provider) machine.stub(ui: Vagrant::UI::Silent.new) + machine.stub(state: nil) results = [] instance.with_target_vms(name.to_s) { |m| results << m } @@ -188,6 +219,7 @@ describe Vagrant::Plugin::V2::Command do environment.stub(primary_machine_name: name) vmware_vm.stub(name: name, provider: provider) vmware_vm.stub(ui: Vagrant::UI::Silent.new) + vmware_vm.stub(state: nil) vms = [] instance.with_target_vms(nil, single_target: true) { |vm| vms << vm } @@ -204,6 +236,7 @@ describe Vagrant::Plugin::V2::Command do environment.stub(primary_machine_name: name) machine.stub(name: name, provider: environment.default_provider) machine.stub(ui: Vagrant::UI::Silent.new) + machine.stub(state: nil) vms = [] instance.with_target_vms(nil, single_target: true) { |vm| vms << machine } diff --git a/test/unit/vagrant/plugin/v2/manager_test.rb b/test/unit/vagrant/plugin/v2/manager_test.rb index 45ebd5a0c..3ddbf00e9 100644 --- a/test/unit/vagrant/plugin/v2/manager_test.rb +++ b/test/unit/vagrant/plugin/v2/manager_test.rb @@ -189,6 +189,42 @@ describe Vagrant::Plugin::V2::Manager do expect(instance.provider_configs[:bar]).to eq("bar") end + it "should enumerate registered push classes" do + pA = plugin do |p| + p.push("foo") { "bar" } + end + + pB = plugin do |p| + p.push("bar", foo: "bar") { "baz" } + end + + instance.register(pA) + instance.register(pB) + + expect(instance.pushes.to_hash.length).to eq(2) + expect(instance.pushes[:foo]).to eq(["bar", nil]) + expect(instance.pushes[:bar]).to eq(["baz", { foo: "bar" }]) + end + + it "provides the collection of registered push configs" do + pA = plugin do |p| + p.config("foo", :push) { "foo" } + end + + pB = plugin do |p| + p.config("bar", :push) { "bar" } + p.config("baz") { "baz" } + end + + instance.register(pA) + instance.register(pB) + + expect(instance.push_configs.to_hash.length).to eq(2) + expect(instance.push_configs[:foo]).to eq("foo") + expect(instance.push_configs[:bar]).to eq("bar") + end + + it "should enumerate all registered synced folder implementations" do pA = plugin do |p| p.synced_folder("foo") { "bar" } diff --git a/test/unit/vagrant/plugin/v2/plugin_test.rb b/test/unit/vagrant/plugin/v2/plugin_test.rb index 944d38f9f..76ad40993 100644 --- a/test/unit/vagrant/plugin/v2/plugin_test.rb +++ b/test/unit/vagrant/plugin/v2/plugin_test.rb @@ -322,6 +322,42 @@ describe Vagrant::Plugin::V2::Plugin do end end + describe "pushes" do + it "should register implementations" do + plugin = Class.new(described_class) do + push("foo") { "bar" } + end + + expect(plugin.components.pushes[:foo]).to eq(["bar", nil]) + end + + it "should be able to specify priorities" do + plugin = Class.new(described_class) do + push("foo", bar: 1) { "bar" } + end + + expect(plugin.components.pushes[:foo]).to eq(["bar", bar: 1]) + end + + it "should lazily register implementations" do + # Below would raise an error if the value of the config class was + # evaluated immediately. By asserting that this does not raise an + # error, we verify that the value is actually lazily loaded + plugin = nil + expect { + plugin = Class.new(described_class) do + push("foo") { raise StandardError, "FAIL!" } + end + }.to_not raise_error + + # Now verify when we actually get the configuration key that + # a proper error is raised. + expect { + plugin.components.pushes[:foo] + }.to raise_error(StandardError) + end + end + describe "synced folders" do it "should register implementations" do plugin = Class.new(described_class) do diff --git a/test/unit/vagrant/registry_test.rb b/test/unit/vagrant/registry_test.rb index d12f46ffe..f177a6a1c 100644 --- a/test/unit/vagrant/registry_test.rb +++ b/test/unit/vagrant/registry_test.rb @@ -90,6 +90,39 @@ describe Vagrant::Registry do expect(result["bar"]).to eq("barvalue") end + describe "#length" do + it "should return 0 when the registry is empty" do + expect(instance.length).to eq(0) + end + + it "should return the number of items in the registry" do + instance.register("foo") { } + instance.register("bar") { } + + expect(instance.length).to eq(2) + end + end + + describe "#size" do + it "should be an alias to #length" do + size = described_class.instance_method(:size) + length = described_class.instance_method(:length) + + expect(size).to eq(length) + end + end + + describe "#empty" do + it "should return true when the registry is empty" do + expect(instance.empty?).to be(true) + end + + it "should return false when there is at least one element" do + instance.register("foo") { } + expect(instance.empty?).to be(false) + end + end + describe "merging" do it "should merge in another registry" do one = described_class.new diff --git a/test/unit/vagrant/ui_test.rb b/test/unit/vagrant/ui_test.rb index 537161a56..e7f2299dd 100644 --- a/test/unit/vagrant/ui_test.rb +++ b/test/unit/vagrant/ui_test.rb @@ -40,23 +40,43 @@ describe Vagrant::UI::Basic do subject.output("foo", new_line: false) end - it "outputs to stdout" do + it "outputs to the assigned stdout" do + stdout = StringIO.new + subject.stdout = stdout + expect(subject).to receive(:safe_puts).with { |message, **opts| - expect(opts[:io]).to be($stdout) + expect(opts[:io]).to be(stdout) true } subject.output("foo") end - it "outputs to stderr for errors" do + it "outputs to stdout by default" do + expect(subject.stdout).to be($stdout) + end + + it "outputs to the assigned stderr for errors" do + stderr = StringIO.new + subject.stderr = stderr + expect(subject).to receive(:safe_puts).with { |message, **opts| - expect(opts[:io]).to be($stderr) + expect(opts[:io]).to be(stderr) true } subject.error("foo") end + + it "outputs to stderr for errors by default" do + expect(subject.stderr).to be($stderr) + end + end + + context "#color?" do + it "returns false" do + expect(subject.color?).to be(false) + end end context "#detail" do @@ -81,6 +101,12 @@ end describe Vagrant::UI::Colored do include_context "unit" + describe "#color?" do + it "returns true" do + expect(subject.color?).to be(true) + end + end + describe "#detail" do it "colors output nothing by default" do expect(subject).to receive(:safe_puts).with("\033[0mfoo\033[0m", anything) diff --git a/test/unit/vagrant/util/downloader_test.rb b/test/unit/vagrant/util/downloader_test.rb index 68f7378b9..f89c6cbe9 100644 --- a/test/unit/vagrant/util/downloader_test.rb +++ b/test/unit/vagrant/util/downloader_test.rb @@ -72,6 +72,27 @@ describe Vagrant::Util::Downloader do expect(subject.download!).to be_true end end + + context "with an urlescaped username and password" do + it "downloads the file with unescaped credentials" do + original_source = source + source = "http://fo%5Eo:b%40r@baz.com/box.box" + subject = described_class.new(source, destination) + + i = curl_options.index(original_source) + curl_options[i] = "http://baz.com/box.box" + + i = curl_options.index("--output") + curl_options.insert(i, "fo^o:b@r") + curl_options.insert(i, "-u") + + expect(Vagrant::Util::Subprocess).to receive(:execute). + with("curl", *curl_options). + and_return(subprocess_result) + + expect(subject.download!).to be_true + end + end end describe "#head" do diff --git a/test/unit/vagrant/util/hash_with_indifferent_access_test.rb b/test/unit/vagrant/util/hash_with_indifferent_access_test.rb index 397773eed..fdebeb374 100644 --- a/test/unit/vagrant/util/hash_with_indifferent_access_test.rb +++ b/test/unit/vagrant/util/hash_with_indifferent_access_test.rb @@ -22,7 +22,7 @@ describe Vagrant::Util::HashWithIndifferentAccess do it "allows indifferent key lookup" do instance["foo"] = "bar" expect(instance.key?(:foo)).to be - expect(instance.has_key?(:foo)).to be + expect(instance.key?(:foo)).to be expect(instance.include?(:foo)).to be expect(instance.member?(:foo)).to be end diff --git a/test/unit/vagrant/util/keypair_test.rb b/test/unit/vagrant/util/keypair_test.rb new file mode 100644 index 000000000..30ab76eba --- /dev/null +++ b/test/unit/vagrant/util/keypair_test.rb @@ -0,0 +1,34 @@ +require "openssl" + +require File.expand_path("../../../base", __FILE__) + +require "vagrant/util/keypair" + +describe Vagrant::Util::Keypair do + describe ".create" do + it "generates a usable keypair with no password" do + # I don't know how to validate the final return value yet... + pubkey, privkey, _ = described_class.create + + pubkey = OpenSSL::PKey::RSA.new(pubkey) + privkey = OpenSSL::PKey::RSA.new(privkey) + + encrypted = pubkey.public_encrypt("foo") + decrypted = privkey.private_decrypt(encrypted) + + expect(decrypted).to eq("foo") + end + + it "generates a keypair that requires a password" do + pubkey, privkey, _ = described_class.create("password") + + pubkey = OpenSSL::PKey::RSA.new(pubkey) + privkey = OpenSSL::PKey::RSA.new(privkey, "password") + + encrypted = pubkey.public_encrypt("foo") + decrypted = privkey.private_decrypt(encrypted) + + expect(decrypted).to eq("foo") + end + end +end diff --git a/test/unit/vagrant/util/platform_test.rb b/test/unit/vagrant/util/platform_test.rb index 3e980937f..ae51076ec 100644 --- a/test/unit/vagrant/util/platform_test.rb +++ b/test/unit/vagrant/util/platform_test.rb @@ -10,4 +10,10 @@ describe Vagrant::Util::Platform do expect(described_class.fs_real_path("c:/foo").to_s).to eql("C:/foo") end end + + describe "#windows_unc_path" do + it "correctly converts a path" do + expect(described_class.windows_unc_path("c:/foo").to_s).to eql("\\\\?\\c:\\foo") + end + end end diff --git a/test/unit/vagrant/vagrantfile_test.rb b/test/unit/vagrant/vagrantfile_test.rb index 8e5b3584c..1354587a2 100644 --- a/test/unit/vagrant/vagrantfile_test.rb +++ b/test/unit/vagrant/vagrantfile_test.rb @@ -26,7 +26,7 @@ describe Vagrant::Vagrantfile do # A helper to register a provider for use in tests. def register_provider(name, config_class=nil, options=nil) - provider_cls = Class.new(Vagrant.plugin("2", :provider)) do + provider_cls = Class.new(VagrantTests::DummyProvider) do if options && options[:unusable] def self.usable?(raise_error=false) raise Vagrant::Errors::VagrantError if raise_error @@ -149,6 +149,21 @@ describe Vagrant::Vagrantfile do expect(results[:provider_cls]).to equal(provider_cls) end + it "configures without a provider or boxes" do + register_provider("foo") + + configure do |config| + config.vm.box = "foo" + end + + results = subject.machine_config(:default, nil, nil) + box = results[:box] + config = results[:config] + expect(config.vm.box).to eq("foo") + expect(box).to be_nil + expect(results[:provider_cls]).to be_nil + end + it "configures with sub-machine config" do register_provider("foo") diff --git a/test/unit/vagrant_test.rb b/test/unit/vagrant_test.rb index 03bb1126e..06f7489ca 100644 --- a/test/unit/vagrant_test.rb +++ b/test/unit/vagrant_test.rb @@ -74,6 +74,17 @@ describe Vagrant do expect(described_class.has_plugin?("foo")).to be_true expect(described_class.has_plugin?("bar")).to be_false end + + it "finds plugins by gem name and version" do + specs = [Gem::Specification.new] + specs[0].name = "foo" + specs[0].version = "1.2.3" + Vagrant::Plugin::Manager.instance.stub(installed_specs: specs) + + expect(described_class.has_plugin?("foo", "~> 1.2.0")).to be_true + expect(described_class.has_plugin?("foo", "~> 1.0.0")).to be_false + expect(described_class.has_plugin?("bar", "~> 1.2.0")).to be_false + end end describe "require_version" do @@ -87,4 +98,23 @@ describe Vagrant do to raise_error(Vagrant::Errors::VagrantVersionBad) end end + + describe "original_env" do + before do + ENV["VAGRANT_OLD_ENV_foo"] = "test" + ENV["VAGRANT_OLD_ENV_bar"] = "test" + end + + after do + ENV["VAGRANT_OLD_ENV_foo"] = "test" + ENV["VAGRANT_OLD_ENV_bar"] = "test" + end + + it "should return the original environment" do + expect(Vagrant.original_env).to eq( + "foo" => "test", + "bar" => "test", + ) + end + end end diff --git a/vagrant.gemspec b/vagrant.gemspec index c4131b270..1748b96af 100644 --- a/vagrant.gemspec +++ b/vagrant.gemspec @@ -15,27 +15,29 @@ Gem::Specification.new do |s| s.required_rubygems_version = ">= 1.3.6" s.rubyforge_project = "vagrant" - s.add_dependency "bundler", ">= 1.5.2", "< 1.7.0" + s.add_dependency "bundler", ">= 1.5.2", "<= 1.10.5" s.add_dependency "childprocess", "~> 0.5.0" s.add_dependency "erubis", "~> 2.7.0" - s.add_dependency "i18n", "~> 0.6.0" - s.add_dependency "listen", "~> 2.7.1" + s.add_dependency "i18n", ">= 0.6.0", "<= 0.8.0" + s.add_dependency "listen", "~> 3.0.2" s.add_dependency "hashicorp-checkpoint", "~> 0.1.1" s.add_dependency "log4r", "~> 1.1.9", "< 1.1.11" s.add_dependency "net-ssh", ">= 2.6.6", "< 2.10.0" + s.add_dependency "net-sftp", "~> 2.1" s.add_dependency "net-scp", "~> 1.1.0" s.add_dependency "rb-kqueue", "~> 0.2.0" + s.add_dependency "rest-client", ">= 1.6.0", "< 2.0" s.add_dependency "wdm", "~> 0.1.0" - s.add_dependency "winrm", "~> 1.1.3" + s.add_dependency "winrm", "~> 1.3" + s.add_dependency "winrm-fs", "~> 0.2.0" # We lock this down to avoid compilation issues. s.add_dependency "nokogiri", "= 1.6.3.1" s.add_development_dependency "rake" - s.add_development_dependency "contest", ">= 0.1.2" - s.add_development_dependency "minitest", "~> 2.5.1" - s.add_development_dependency "mocha" s.add_development_dependency "rspec", "~> 2.14.0" + s.add_development_dependency "webmock", "~> 1.20" + s.add_development_dependency "fake_ftp", "~> 0.1" # The following block of code determines the files that should be included # in the gem. It does this by reading all the files in the directory where diff --git a/version.txt b/version.txt index 9f05f9f2c..13bf18c2d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.6.5 +1.7.4.dev diff --git a/website/docs/Gemfile b/website/docs/Gemfile index 5f8d1e016..3534fecfc 100644 --- a/website/docs/Gemfile +++ b/website/docs/Gemfile @@ -1,11 +1,13 @@ source "https://rubygems.org" +ruby "2.2.2" + gem "less", "~> 2.2.2" gem "middleman", "~> 3.0.6" gem "middleman-minify-html", "~> 3.0.0" gem "rack-contrib", "~> 1.1.0" gem "redcarpet", "~> 2.2.2" -gem "rb-inotify", "~> 0.9" +gem "rb-inotify", "~> 0.9", require: false gem "therubyracer", "~> 0.12.0" gem "thin", "~> 1.5.0" diff --git a/website/docs/Gemfile.lock b/website/docs/Gemfile.lock index c88f8653b..b0ae81928 100644 --- a/website/docs/Gemfile.lock +++ b/website/docs/Gemfile.lock @@ -1,42 +1,44 @@ GEM remote: https://rubygems.org/ specs: - POpen4 (0.1.4) - Platform (>= 0.4.0) - open4 - Platform (0.4.0) activesupport (3.2.13) i18n (= 0.6.1) multi_json (~> 1.0) - chunky_png (1.2.8) + chunky_png (1.3.4) coffee-script (2.2.0) coffee-script-source execjs coffee-script-source (1.3.3) commonjs (0.2.7) - compass (0.12.2) + compass (1.0.3) chunky_png (~> 1.2) - fssm (>= 0.2.7) - sass (~> 3.1) - daemons (1.1.9) - eventmachine (1.0.3) - execjs (1.4.0) + compass-core (~> 1.0.2) + compass-import-once (~> 1.0.5) + rb-fsevent (>= 0.9.3) + rb-inotify (>= 0.9) + sass (>= 3.3.13, < 3.5) + compass-core (1.0.3) multi_json (~> 1.0) - ffi (1.9.3) - fssm (0.2.10) - haml (4.0.3) + sass (>= 3.3.0, < 3.5) + compass-import-once (1.0.5) + sass (>= 3.2, < 3.5) + daemons (1.1.9) + eventmachine (1.0.7) + execjs (1.4.1) + multi_json (~> 1.0) + ffi (1.9.6) + haml (4.0.6) tilt - highline (1.6.19) + highline (1.6.21) hike (1.2.3) - htmlcompressor (0.0.7) - yui-compressor (~> 0.9.6) + htmlcompressor (0.1.2) http_router (0.10.2) rack (>= 1.0.0) url_mount (~> 0.2.1) i18n (0.6.1) less (2.2.2) commonjs (~> 0.2.6) - libv8 (3.16.14.3) + libv8 (3.16.14.7) listen (0.7.3) maruku (0.6.1) syntax (>= 1.0.0) @@ -74,8 +76,7 @@ GEM sprockets (~> 2.1) sprockets-helpers (~> 1.0.0) sprockets-sass (~> 1.0.0) - multi_json (1.8.0) - open4 (1.3.0) + multi_json (1.10.1) padrino-core (0.10.7) activesupport (~> 3.2.0) http_router (~> 0.10.2) @@ -88,32 +89,32 @@ GEM rack (1.4.5) rack-contrib (1.1.0) rack (>= 0.9.1) - rack-protection (1.5.0) + rack-protection (1.5.3) rack - rack-test (0.6.2) + rack-test (0.6.3) rack (>= 1.0) - rb-fsevent (0.9.3) - rb-inotify (0.9.3) + rb-fsevent (0.9.4) + rb-inotify (0.9.5) ffi (>= 0.5.0) redcarpet (2.2.2) ref (1.0.5) - sass (3.2.10) + sass (3.4.13) sinatra (1.3.6) rack (~> 1.4) rack-protection (~> 1.3) tilt (~> 1.3, >= 1.3.3) - sprockets (2.10.0) + sprockets (2.12.3) hike (~> 1.2) multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) sprockets-helpers (1.0.1) sprockets (~> 2.0) - sprockets-sass (1.0.1) + sprockets-sass (1.0.3) sprockets (~> 2.0) tilt (~> 1.1) - syntax (1.0.0) - therubyracer (0.12.0) + syntax (1.2.0) + therubyracer (0.12.1) libv8 (~> 3.16.14.0) ref thin (1.5.1) @@ -127,8 +128,6 @@ GEM multi_json (~> 1.3) url_mount (0.2.1) rack - yui-compressor (0.9.6) - POpen4 (>= 0.1.4) PLATFORMS ruby diff --git a/website/docs/Vagrantfile b/website/docs/Vagrantfile index 7e26cbed8..34e232f31 100644 --- a/website/docs/Vagrantfile +++ b/website/docs/Vagrantfile @@ -1,22 +1,21 @@ # -*- mode: ruby -*- # vi: set ft=ruby : -# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! -VAGRANTFILE_API_VERSION = "2" - $script = < - - - @@ -150,6 +138,7 @@ >Tips & Tricks >config.vm >config.ssh + >config.winrm >config.vagrant <% end %> @@ -172,7 +161,9 @@ >Ansible >CFEngine >Chef Solo + >Chef Zero >Chef Client + >Chef Apply >Docker >Puppet Apply >Puppet Agent @@ -252,6 +243,7 @@ >Boxes >Configuration >Known Issues + >Kernel Upgrade <% end %> <% end %> @@ -285,6 +277,17 @@ <% end %> + >Push + + <% if sidebar_section == "push" %> + + <% end %> + >Other <% if sidebar_section == "other" %> @@ -341,7 +344,7 @@
@@ -355,5 +358,39 @@ + + + + + + diff --git a/website/docs/source/stylesheets/_base.less b/website/docs/source/stylesheets/_base.less index d78333d04..68790ad40 100644 --- a/website/docs/source/stylesheets/_base.less +++ b/website/docs/source/stylesheets/_base.less @@ -1,11 +1,11 @@ html { - font-size: 100%; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; - text-rendering: optimizeLegibility; - -webkit-tap-highlight-color: transparent; -} + font-size: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + text-rendering: optimizeLegibility; + -webkit-tap-highlight-color: transparent; +} body { font-family: @sans-serif-stack; font-size: @base-font-size; @@ -14,17 +14,18 @@ body { background-color: @white; letter-spacing: 2px; .museo-sans-regular; -} +} .wrapper { -margin-top: 80px; -} + margin-top: 80px; +} .container { -z-index: 999; //keep content on top -position: relative; -} + z-index: 999; + //keep content on top + position: relative; +} h1, h2, h3, @@ -36,272 +37,298 @@ h6 { color: inherit; text-rendering: optimizelegibility; .museo-sans-bold; + } +h1 { + @font-size: 70px; + font-size: @font-size; + line-height: 80px; + letter-spacing: 3px; - h1 { - @font-size: 70px; - font-size: @font-size; - line-height: 80px; - letter-spacing: 3px; + span { + font-size: @headline-span-size; + display: block; - span { - font-size: @headline-span-size; - display: block; - } + } + &.all-caps { + text-transform: uppercase; + text-align: center; + font-size: 40px; + } - &.all-caps { - text-transform: uppercase; - text-align: center; - font-size: 40px; - } - } - - h2 { - @font-size: 30px; - font-size: @font-size; - line-height: 35px; - } - - h3 { - @font-size: 30px; - font-size: @font-size; - line-height: @font-size; - } - - h4 { - @font-size: 24px; - font-size: @font-size; - line-height: @font-size; - } - - h5 { - @font-size: 20px; - font-size: @font-size; - line-height: @font-size; - } - - h6 { - @font-size: 12px; - font-size: @font-size; - line-height: @font-size; - } - -p, td { - letter-spacing: normal; - line-height: 32px; - - a { - color: @docs-blue; - text-decoration: none; - border-bottom: 1px solid @docs-blue; - - &:hover { - text-decoration: none; - color: darken(@blue, 10%); - border-bottom: 1px solid darken(@blue, 10%); - } - } } +h2 { + @font-size: 30px; + font-size: @font-size; + line-height: 35px; +} +h3 { + @font-size: 30px; + font-size: @font-size; + line-height: @font-size; + +} +h4 { + @font-size: 24px; + font-size: @font-size; + line-height: @font-size; + +} +h5 { + @font-size: 20px; + font-size: @font-size; + line-height: @font-size; + +} +h6 { + @font-size: 12px; + font-size: @font-size; + line-height: @font-size; + +} +p, +td { + letter-spacing: normal; + line-height: 32px; + + a { + color: @docs-blue; + text-decoration: none; + border-bottom: 1px solid @docs-blue; + + &:hover { + text-decoration: none; + color: darken(@blue, 10%); + border-bottom: 1px solid darken(@blue, 10%); + } + } + +} a { - color: inherit; - text-decoration: none; + color: inherit; + text-decoration: none; + &:hover { + text-decoration: none; + color: @purple; + .animate-text-color; - &:hover { - text-decoration: none; - color: @purple; - .animate-text-color; - } + } + &:active { + color: @blue; - &:active { - color: @blue; - } + } + &:visited { - &:visited { + } - } } - ul { -} +} li { -line-height: @base-line-height; -} + line-height: @base-line-height; +} blockquote { - border: none; + border: none; margin: 60px; - p { // blockquote p - font-size: @base-font-size * 2; - line-height: @base-line-height * 2; - font-style: italic; + p { + // blockquote p + font-size: @base-font-size * 2; + line-height: @base-line-height * 2; + font-style: italic; } -} +} strong { -.museo-sans-bold; -} + .museo-sans-bold; +} em { -.museo-sans-regular-italic; -} + .museo-sans-regular-italic; +} br { -display:block; -line-height: (@baseline * 2); -} + display: block; + line-height: (@baseline * 2); +} pre, code { -font-family: @mono-stack; -} + font-family: @mono-stack; +} code { -font-size: inherit; -} + font-size: inherit; +} pre { -border: none; -font-size: @base-font-size; -background: @black; -color: @white; -padding: 20px; -line-height: @base-line-height; -margin-top: 20px; -margin-bottom: 20px; + border: none; + font-size: @base-font-size; + background: @black; + color: @white; + padding: 20px; + line-height: @base-line-height; + margin-top: 20px; + margin-bottom: 20px; + + span { + color: @code-highlight-text; + } - span { - color: @code-highlight-text; - } } - hr { -} +} .vr { -width: 2px; -height: 100%; -} + width: 2px; + height: 100%; +} form { -} +} input { -letter-spacing: 3px; + letter-spacing: 3px; - &:focus { - outline: none; - } + &:focus { + outline: none; + } } +::-webkit-input-placeholder { - ::-webkit-input-placeholder { - overflow: visible; - padding-top: 3px; - color: @light-gray-text; - } - - input:-moz-placeholder { - overflow: visible; - padding-top: 3px; - color: @light-gray-text; - } + overflow: visible; + padding-top: 3px; + color: @light-gray-text; +} +input:-moz-placeholder { + overflow: visible; + padding-top: 3px; + color: @light-gray-text; +} /* type and styles */ .meta, .legal, .date { -color: @medium-gray-text; -line-height: @base-line-height; -.museo-sans-regular; + color: @medium-gray-text; + line-height: @base-line-height; + .museo-sans-regular; + } +.date { + text-transform: uppercase; - .date { - text-transform: uppercase; - } - +} .button { -color: @white; -text-align: center; -background-color: @primary-button-color; -display: block; -padding: 15px 0; -margin-top: 20px !important; -text-transform: uppercase; -font-size: 25px; -letter-spacing: 5px; -.museo-sans-light; -.rounded; -.hover; + color: @white; + text-align: center; + background-color: @primary-button-color; + display: block; + padding: 15px 0; + margin-top: 20px !important; + text-transform: uppercase; + font-size: 25px; + letter-spacing: 5px; + .museo-sans-light; + .rounded; + .hover; - &.inline-button { - background-color: @vagrant-blue; - padding: 5px 20px; - color: @white !important; - font-size: 15px; - letter-spacing: 1px; - .rounded; + &.inline-button { + background-color: @vagrant-blue; + padding: 5px 20px; + color: @white !important; + font-size: 15px; + letter-spacing: 1px; + .rounded; + + a, + a:hover { + color: @white; + } + &.next-button, + &.prev-button { + max-width: 33%; + white-space: nowrap; + } + &.next-button { + float: right; + &:before { + content: "Next:"; + display: inline-block; + padding-right: 3px; + } + &:after { + content: "\00BB"; + display: inline-block; + padding-left: 3px; + } + + } + &.prev-button { + float: left; + &:before { + content: "\00AB\0020Previous: "; + display: inline-block; + padding-right: 3px; + } + } - a, - a:hover { - color: @white; } -} + &.white-button { + background: fade(@white, 20%); -&.white-button { - background: fade(@white, 20%); + &:hover { + background: fade(@white, 30%); + } + } + &.secondary-button { + background: @light-gray; + + &:hover { + background: @purple; + } + + } + &.with-carat span { + margin-right: -10px; + //recenter text if there's a carat after text + + } + span { + // button text styles can go here + + } &:hover { - background: fade(@white, 30%); + background-color: @purple; + .animate-background-color; + } -} + &:active { -&.secondary-button { - background: @light-gray; - &:hover { - background: @purple; } -} - -&.with-carat span { - margin-right: -10px; //recenter text if there's a carat after text + &.disabled { + background-color: @light-gray-background; + } } - -span { - // button text styles can go here -} - -&:hover { - background-color: @purple; - .animate-background-color; -} - -&:active { - -} - -&.disabled { - background-color: @light-gray-background; -} - -} - a.read-more { color: @blue; &:hover { color: darken(@blue, 10%); } -} +} // misc. styles .loading { text-align: center; @@ -310,8 +337,8 @@ a.read-more { letter-spacing: 5px; color: @medium-gray-text; padding: 30px 0 20px; -} -.pinned { - position:fixed; +} +.pinned { + position: fixed; } diff --git a/website/docs/source/v2/boxes.html.md b/website/docs/source/v2/boxes.html.md index e3c1661de..e123b6c2d 100644 --- a/website/docs/source/v2/boxes.html.md +++ b/website/docs/source/v2/boxes.html.md @@ -14,7 +14,7 @@ boxes. You can read the documentation on the [vagrant box](/v2/cli/box.html) command for more information. The easiest way to use a box is to add a box from the -[publicly available catalog of Vagrant boxes](https://vagrantcloud.com). +[publicly available catalog of Vagrant boxes](https://atlas.hashicorp.com/boxes/search). You can also add and share your own customized boxes on this website. Boxes also support versioning so that members of your team using Vagrant @@ -27,7 +27,7 @@ sub-pages in the navigation to the left. ## Discovering Boxes The easiest way to find boxes is to look on the -[public Vagrant box catalog](https://vagrantcloud.com) +[public Vagrant box catalog](https://atlas.hashicorp.com/boxes/search) for a box matching your use case. The catalog contains most major operating systems as bases, as well as specialized boxes to get you up and running quickly with LAMP stacks, Ruby, Python, etc. diff --git a/website/docs/source/v2/boxes/base.html.md b/website/docs/source/v2/boxes/base.html.md index 282efd4fc..4a6ec2ca1 100644 --- a/website/docs/source/v2/boxes/base.html.md +++ b/website/docs/source/v2/boxes/base.html.md @@ -93,7 +93,7 @@ can be easily added via the Vagrantfile in most cases. Just about every aspect of Vagrant can be modified. However, Vagrant does expect some defaults which will cause your base box to "just work" out -of the box. You should create these as defaults if you intent to publicly +of the box. You should create these as defaults if you intend to publicly distribute your box. If you're creating a base box for private use, you should try _not_ to @@ -116,6 +116,10 @@ that OpenSSH is very picky about file permissions. Therefore, make sure that `~/.ssh` has `0700` permissions and the authorized keys file has `0600` permissions. +When Vagrant boots a box and detects the insecure keypair, it will +automatically replace it with a randomly generated keypair for additional +security while the box is running. + ### Root Password: "vagrant" Vagrant doesn't actually use or expect any root password. However, having @@ -157,6 +161,63 @@ in the SSH server configuration. This avoids a reverse DNS lookup on the connecting SSH client which can take many seconds. +## Windows Boxes + +Supported Windows guest operating systems: +- Windows 7 +- Windows 8 +- Windows Server 2008 +- Windows Server 2008 R2 +- Windows Server 2012 +- Windows Server 2012 R2 + +Windows Server 2003 and Windows XP are _not_ supported, but if you're a die +hard XP fan [this](http://stackoverflow.com/a/18593425/18475) may help you. + +### Base Windows Configuration + + - Turn off UAC + - Disable complex passwords + - Disable "Shutdown Tracker" + - Disable "Server Manager" starting at login (for non-Core) + +In addition to disabling UAC in the control panel, you also must disable +UAC in the registry. This may vary from Windows version to Windows version, +but Windows 8/8.1 use the command below. This will allow some things like +automated Puppet installs to work within Vagrant Windows base boxes. + + reg add HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System /v EnableLUA /d 0 /t REG_DWORD /f /reg:64 + +### Base WinRM Configuration + +To enable and configure WinRM you'll need to set the WinRM service to +auto-start and allow unencrypted basic auth (obviously this is not secure). +Run the following commands from a regular Windows command prompt: +``` +winrm quickconfig -q +winrm set winrm/config/winrs @{MaxMemoryPerShellMB="512"} +winrm set winrm/config @{MaxTimeoutms="1800000"} +winrm set winrm/config/service @{AllowUnencrypted="true"} +winrm set winrm/config/service/auth @{Basic="true"} +sc config WinRM start= auto +``` + +### Additional WinRM 1.1 Configuration + +These additional configuration steps are specific to Windows Server 2008 +(WinRM 1.1). For Windows Server 2008 R2, Windows 7 and later versions of +Windows you can ignore this section. + +1. Ensure the Windows PowerShell feature is installed +2. Change the WinRM port to 5985 or upgrade to WinRM 2.0 + +The following commands will change the WinRM 1.1 port to what's expected by +Vagrant: +``` +netsh firewall add portopening TCP 5985 "Port 5985" +winrm set winrm/config/listener?Address=*+Transport=HTTP @{Port="5985"} +``` + ## Other Software At this point, you have all the common software you absolutely _need_ for @@ -185,7 +246,7 @@ provider-specific guides are linked to towards the top of this page. You can distribute the box file however you'd like. However, if you want to support versioning, putting multiple providers at a single URL, pushing updates, analytics, and more, we recommend you add the box to -[Vagrant Cloud](https://vagrantcloud.com). +[HashiCorp's Atlas](https://atlas.hashicorp.com). You can upload both public and private boxes to this service. diff --git a/website/docs/source/v2/boxes/format.html.md b/website/docs/source/v2/boxes/format.html.md index 0eb584026..3fa50479b 100644 --- a/website/docs/source/v2/boxes/format.html.md +++ b/website/docs/source/v2/boxes/format.html.md @@ -23,7 +23,7 @@ Today, there are two different components: box file and so on. * Box Catalog Metadata - This is a JSON document (typically exchanged - during interactions with [Vagrant Cloud](https://vagrantcloud.com)) + during interactions with [HashiCorp's Atlas](https://atlas.hashicorp.com)) that specifies the name of the box, a description, available versions, available providers, and URLs to the actual box files (next component) for each provider and version. If this catalog @@ -78,8 +78,8 @@ providers from a single file, and more.
You don't need to manually make the metadata. If you -have an account with Vagrant Cloud, you -can create boxes there, and Vagrant Cloud automatically creates +have an account with HashiCorp's Atlas, you +can create boxes there, and HashiCorp's Atlas automatically creates the metadata for you. The format is still documented here.
diff --git a/website/docs/source/v2/boxes/versioning.html.md b/website/docs/source/v2/boxes/versioning.html.md index 61af2345a..dc4cf3e28 100644 --- a/website/docs/source/v2/boxes/versioning.html.md +++ b/website/docs/source/v2/boxes/versioning.html.md @@ -24,10 +24,10 @@ to update your own custom boxes with versions. That is covered in `vagrant box list` only shows _installed_ versions of boxes. If you want to see all available versions of a box, you'll have to find the box -on [Vagrant Cloud](https://vagrantcloud.com). An easy way to find a box -is to use the url `https://vagrantcloud.com/USER/BOX`. For example, for +on [HashiCorp's Atlas](https://atlas.hashicorp.com). An easy way to find a box +is to use the url `https://atlas.hashicorp.com/USER/BOX`. For example, for the `hashicorp/precise64` box, you can find information about it at -`https://vagrantcloud.com/hashicorp/precise64`. +`https://atlas.hashicorp.com/hashicorp/precise64`. You can check if the box you're using is outdated with `vagrant box outdated`. This can check if the box in your current Vagrant environment is outdated diff --git a/website/docs/source/v2/cli/box.html.md b/website/docs/source/v2/cli/box.html.md index f071d9291..e919fc9ad 100644 --- a/website/docs/source/v2/cli/box.html.md +++ b/website/docs/source/v2/cli/box.html.md @@ -26,10 +26,10 @@ This adds a box with the given address to Vagrant. The address can be one of three things: * A shorthand name from the -[public catalog of available Vagrant images](https://vagrantcloud.com), +[public catalog of available Vagrant images](https://atlas.hashicorp.com/boxes/search), such as "hashicorp/precise64". -* File path or HTTP URL to a box in a [catalog](https://vagrantcloud.com). +* File path or HTTP URL to a box in a [catalog](https://atlas.hashicorp.com/boxes/search). For HTTP, basic authentication is supported and `http_proxy` environmental variables are respected. HTTPS is also supported. @@ -93,8 +93,8 @@ you're not using a catalog). to be specified.
-Checksums for versioned boxes or boxes from Vagrant Cloud: -For boxes from Vagrant Cloud, the checksums are embedded in the metadata +Checksums for versioned boxes or boxes from HashiCorp's Atlas: +For boxes from HashiCorp's Atlas, the checksums are embedded in the metadata of the box. The metadata itself is served over TLS and its format is validated.
@@ -144,10 +144,11 @@ what versions to delete with the `--box-version` flag. # Box Repackage -**Command: `vagrant box repackage NAME PROVIDER`** +**Command: `vagrant box repackage NAME PROVIDER VERSION`** This command repackages the given box and puts it in the current -directory so you can redistribute it. +directory so you can redistribute it. The name, provider, and version +of the box can be retrieved using `vagrant box list`. When you add a box, Vagrant unpacks it and stores it internally. The original `*.box` file is not preserved. This command is useful for diff --git a/website/docs/source/v2/cli/destroy.html.md b/website/docs/source/v2/cli/destroy.html.md index 8b16e7973..9ed12ea11 100644 --- a/website/docs/source/v2/cli/destroy.html.md +++ b/website/docs/source/v2/cli/destroy.html.md @@ -18,3 +18,15 @@ confirmation can be skipped by passing in the `-f` or `--force` flag. ## Options * `-f` or `--force` - Don't ask for confirmation before destroying. + +
+

+ The vagrant destroy command does not remove a box + that may have been installed on your computer during vagrant up. + Thus, even if you run vagrant destroy, the box installed in the system + will still be present on the hard drive. To return your computer to the + state as it was before vagrant up command, you need to use + vagrant box remove. For more information, read about the + [vagrant box remove](/v2/cli/box.html) command. +

+
diff --git a/website/docs/source/v2/cli/index.html.md b/website/docs/source/v2/cli/index.html.md index 7bc55a43e..345a902ab 100644 --- a/website/docs/source/v2/cli/index.html.md +++ b/website/docs/source/v2/cli/index.html.md @@ -22,3 +22,8 @@ accepts. In depth documentation and use cases of various Vagrant commands is available by reading the appropriate sub-section available in the left navigational area of this site. + +You may also wish to consult the +[documentation](/v2/other/environmental-variables.html) regarding the +environmental variables that can be used to configure and control +Vagrant in a global way. diff --git a/website/docs/source/v2/cli/login.html.md b/website/docs/source/v2/cli/login.html.md index 5e2e56065..1ff6e9c6d 100644 --- a/website/docs/source/v2/cli/login.html.md +++ b/website/docs/source/v2/cli/login.html.md @@ -7,8 +7,8 @@ sidebar_current: "cli-login" **Command: `vagrant login`** -The login command is used to authenticate with a -[Vagrant Cloud](https://vagrantcloud.com) server. Logging is only +The login command is used to authenticate with the +[HashiCorp's Atlas](https://atlas.hashicorp.com) server. Logging is only necessary if you're accessing protected boxes or using [Vagrant Share](/v2/share/index.html). @@ -19,6 +19,7 @@ boxes or [Vagrant Share](/v2/share/index.html) require a login. The reference of available command-line flags to this command is available below. + ## Options * `--check` - This will check if you're logged in. In addition to outputting @@ -28,3 +29,32 @@ is available below. * `--logout` - This will log you out if you're logged in. If you're already logged out, this command will do nothing. It is not an error to call this command if you're already logged out. + +* `--token` - This will set the Atlas login token manually to the provided + string. It is assumed this token is a valid Atlas access token. + + +## Examples + +Securely authenticate to Atlas using a username and password: + +```text +$ vagrant login +# ... +Atlas username: +Atlas password: +``` + +Check if the current user is authenticated: + +```text +$ vagrant login --check +You are already logged in. +``` + +Securely authenticate with Atlas using a token: + +```text +$ vagrant login --token ABCD1234 +The token was successfully saved. +``` diff --git a/website/docs/source/v2/cli/plugin.html.md b/website/docs/source/v2/cli/plugin.html.md index 9ff754059..45bb75a4f 100644 --- a/website/docs/source/v2/cli/plugin.html.md +++ b/website/docs/source/v2/cli/plugin.html.md @@ -32,6 +32,9 @@ If multiple names are specified, multiple plugins will be installed. If flags are given below, the flags will apply to _all_ plugins being installed by the current command invocation. +If the plugin is already installed, this command will reinstall it with +the latest version available. + This command accepts optional command-line flags: * `--entry-point ENTRYPOINT` - By default, installed plugins are loaded diff --git a/website/docs/source/v2/cli/rsync.html.md b/website/docs/source/v2/cli/rsync.html.md index 7d8f420ba..4239e6e82 100644 --- a/website/docs/source/v2/cli/rsync.html.md +++ b/website/docs/source/v2/cli/rsync.html.md @@ -9,3 +9,7 @@ sidebar_current: "cli-rsync" This command forces a resync of any [rsync synced folders](/v2/synced-folders/rsync.html). + +Note that if you change any settings within the rsync synced folders such +as exclude paths, you'll need to `vagrant reload` before this command will +pick up those changes. diff --git a/website/docs/source/v2/cli/ssh.html.md b/website/docs/source/v2/cli/ssh.html.md index 68685cbac..0950db0a9 100644 --- a/website/docs/source/v2/cli/ssh.html.md +++ b/website/docs/source/v2/cli/ssh.html.md @@ -17,8 +17,18 @@ any arbitrary commands to do things such as reverse tunneling down into the ## Options * `-c COMMAND` or `--command COMMAND` - This executes a single SSH command, prints - out the stdout and stderr, and exits. stdin will not be functional on this - executed command. + out the stdout and stderr, and exits. * `-p` or `--plain` - This does an SSH without authentication, leaving authentication up to the user. + +## Background Execution + +If the command you specify runs in the background (such as appending a `&` to +a shell command), it will be terminated almost immediately. This is because +when Vagrant executes the command, it executes it within the context of a +shell, and when the shell exits, all of the child processes also exit. + +To avoid this, you'll need to detach the process from the shell. Please +Google to learn how to do this for your shell. One method of doing this is +the `nohup` command. diff --git a/website/docs/source/v2/cli/up.html.md b/website/docs/source/v2/cli/up.html.md index fdbbcccbb..a0408a9a6 100644 --- a/website/docs/source/v2/cli/up.html.md +++ b/website/docs/source/v2/cli/up.html.md @@ -21,7 +21,8 @@ on a day-to-day basis. By default this is set. * `--[no-]parallel` - Bring multiple machines up in parallel if the provider - supports it. + supports it. Please consult the provider documentation to see if this feature + is supported. * `--provider x` - Bring the machine up with the given [provider](/v2/providers/index.html). By default this is "virtualbox". diff --git a/website/docs/source/v2/docker/basics.html.md b/website/docs/source/v2/docker/basics.html.md index 6f2f41c48..9f4dd97a6 100644 --- a/website/docs/source/v2/docker/basics.html.md +++ b/website/docs/source/v2/docker/basics.html.md @@ -65,6 +65,8 @@ and networking options into Docker volumes and forwarded ports. You don't have to use the Docker-specific configurations to do this. This helps keep your Vagrantfile similar to how it has always looked. +Private and public networks are not currently supported. + ## Host VM On systems that can't run Linux containers natively, such as Mac OS X diff --git a/website/docs/source/v2/docker/configuration.html.md b/website/docs/source/v2/docker/configuration.html.md index 6f8772c3f..6589ef887 100644 --- a/website/docs/source/v2/docker/configuration.html.md +++ b/website/docs/source/v2/docker/configuration.html.md @@ -18,6 +18,8 @@ you may set. A complete reference is shown below. ### Optional +General settings: + * `build_args` (array of strings) - Extra arguments to pass to `docker build` when `build_dir` is in use. @@ -28,13 +30,19 @@ you may set. A complete reference is shown below. `docker run` when the container is started. This can be used to set parameters that aren't exposed via the Vagrantfile. + * `dockerfile` (string) - Name of the Dockerfile in the build directory. + This defaults to "Dockerfile" + * `env` (hash) - Environmental variables to expose into the container. * `expose` (array of integers) - Ports to expose from the container but not to the host machine. Useful for links. * `link` (method, string argument) - Link this container to another - by name. Example: `docker.link("db:db")`. + by name. The argument should be in the format of `(name:alias)`. + Example: `docker.link("db:db")`. Note, if you're linking to + another container in the same Vagrantfile, make sure you call + `vagrant up` with the `--no-parallel` flag. * `force_host_vm` (boolean) - If true, then a host VM will be spun up even if the computer running Vagrant supports Linux containers. This @@ -60,6 +68,9 @@ you may set. A complete reference is shown below. of time. If false, then Vagrant expects that this container will automatically stop at some point, and won't error if it sees it do that. + * `stop_timeout` (integer) - The amount of time to wait when stopping + a container before sending a SIGTERM to the process. + * `vagrant_machine` (string) - The name of the Vagrant machine in the `vagrant_vagrantfile` to use as the host machine. This defaults to "default". @@ -71,3 +82,16 @@ you may set. A complete reference is shown below. volumes into the container. These directories must exist in the host where Docker is running. If you want to sync folders from the host Vagrant is running, just use synced folders. + +Below, we have settings related to auth. If these are set, then Vagrant +will `docker login` prior to starting containers, allowing you to pull +images from private repositories. + + * `email` (string) - Email address for logging in. + + * `username` (string) - Username for logging in. + + * `password` (string) - Password for logging in. + + * `auth_server` (string) - The server to use for authentication. If not + set, the Docker Hub will be used. diff --git a/website/docs/source/v2/getting-started/boxes.html.md b/website/docs/source/v2/getting-started/boxes.html.md index b16aa893e..9d165d2ad 100644 --- a/website/docs/source/v2/getting-started/boxes.html.md +++ b/website/docs/source/v2/getting-started/boxes.html.md @@ -27,8 +27,8 @@ $ vagrant box add hashicorp/precise32 ``` This will download the box named "hashicorp/precise32" from -[Vagrant Cloud](https://vagrantcloud.com), a place where you can find -and host boxes. While it is easiest to download boxes from Vagrant Cloud +[HashiCorp's Atlas box catalog](https://atlas.hashicorp.com/boxes/search), a place where you can find +and host boxes. While it is easiest to download boxes from HashiCorp's Atlas you can also add boxes from a local file, custom URL, etc. Added boxes can be re-used by multiple projects. Each project uses a box @@ -64,11 +64,14 @@ For the remainder of this getting started guide, we'll only use the this getting started guide, the first question you'll probably have is "where do I find more boxes?" -The best place to find more boxes is [Vagrant Cloud](https://vagrantcloud.com). -Vagrant Cloud has a public directory of freely available boxes that -run various platforms and technologies. Vagrant Cloud also has a great search +The best place to find more boxes is [HashiCorp's Atlas box catalog](https://atlas.hashicorp.com/boxes/search). +HashiCorp's Atlas has a public directory of freely available boxes that +run various platforms and technologies. HashiCorp's Atlas also has a great search feature to allow you to find the box you care about. -In addition to finding free boxes, Vagrant Cloud lets you host your own +In addition to finding free boxes, HashiCorp's Atlas lets you host your own boxes, as well as private boxes if you intend on creating boxes for your own organization. + +Project Setup +Up And SSH diff --git a/website/docs/source/v2/getting-started/index.html.md b/website/docs/source/v2/getting-started/index.html.md index fd73de81b..26cbae2cb 100644 --- a/website/docs/source/v2/getting-started/index.html.md +++ b/website/docs/source/v2/getting-started/index.html.md @@ -17,7 +17,7 @@ since it is free, available on every major platform, and built-in to Vagrant. After reading the guide though, don't forget that Vagrant can work with [many other providers](/v2/getting-started/providers.html). -Before diving into your first project, please [install Vagrant](/v2/installation/index.html). +Before diving into your first project, please [install the latest version of Vagrant](/v2/installation/index.html). And because we'll be using [VirtualBox](http://www.virtualbox.org) as our provider for the getting started guide, please install that as well. @@ -39,7 +39,7 @@ $ vagrant up ``` After running the above two commands, you'll have a fully running -virtual machine in [VirtualBox](http://virtualbox.org) running +virtual machine in [VirtualBox](https://www.virtualbox.org) running Ubuntu 12.04 LTS 32-bit. You can SSH into this machine with `vagrant ssh`, and when you're done playing around, you can remove all traces of it with `vagrant destroy`. @@ -54,3 +54,5 @@ comfort of your own machine. The rest of this guide will walk you through setting up a more complete project, covering more features of Vagrant. + +Project Setup diff --git a/website/docs/source/v2/getting-started/networking.html.md b/website/docs/source/v2/getting-started/networking.html.md index 150b4cab1..76484a04a 100644 --- a/website/docs/source/v2/getting-started/networking.html.md +++ b/website/docs/source/v2/getting-started/networking.html.md @@ -26,7 +26,7 @@ is a simple edit to the Vagrantfile, which now looks like this: Vagrant.configure("2") do |config| config.vm.box = "hashicorp/precise32" config.vm.provision :shell, path: "bootstrap.sh" - config.vm.network :forwarded_port, host: 4567, guest: 80 + config.vm.network :forwarded_port, guest: 80, host: 4567 end ``` @@ -43,3 +43,6 @@ Vagrant also has other forms of networking, allowing you to assign a static IP address to the guest machine, or to bridge the guest machine onto an existing network. If you're interested in other options, read the [networking](/v2/networking/index.html) page. + +Provisioning +Share diff --git a/website/docs/source/v2/getting-started/project_setup.html.md b/website/docs/source/v2/getting-started/project_setup.html.md index e36fac01c..3d1bc2706 100644 --- a/website/docs/source/v2/getting-started/project_setup.html.md +++ b/website/docs/source/v2/getting-started/project_setup.html.md @@ -5,8 +5,8 @@ sidebar_current: "gettingstarted-projectsetup" # Project Setup -The first step for any project to use Vagrant is to configure Vagrant -using a [Vagrantfile](/v2/vagrantfile/index.html). The purpose of the +The first step in configuring any Vagrant project is to create a +[Vagrantfile](/v2/vagrantfile/index.html). The purpose of the Vagrantfile is twofold: 1. Mark the root directory of your project. A lot of the configuration @@ -36,3 +36,6 @@ set up Vagrant for an existing project. The Vagrantfile is meant to be committed to version control with your project, if you use version control. This way, every person working with that project can benefit from Vagrant without any upfront work. + +Getting Started +Boxes diff --git a/website/docs/source/v2/getting-started/providers.html.md b/website/docs/source/v2/getting-started/providers.html.md index abda2deec..9dfd31d71 100644 --- a/website/docs/source/v2/getting-started/providers.html.md +++ b/website/docs/source/v2/getting-started/providers.html.md @@ -35,3 +35,5 @@ flags necessary. For more information on providers, read the full documentation on [providers](/v2/providers/index.html). + +Rebuild diff --git a/website/docs/source/v2/getting-started/provisioning.html.md b/website/docs/source/v2/getting-started/provisioning.html.md index ad2b24150..ceb79e82d 100644 --- a/website/docs/source/v2/getting-started/provisioning.html.md +++ b/website/docs/source/v2/getting-started/provisioning.html.md @@ -75,3 +75,6 @@ directory, which is the default synced folder setup by Vagrant. You can play around some more by creating some more files and viewing them from the terminal, but in the next step we'll cover networking options so that you can use your own browser to access the guest machine. + +Synced Folders +Networking diff --git a/website/docs/source/v2/getting-started/rebuild.html.md b/website/docs/source/v2/getting-started/rebuild.html.md index aa5a3bc40..1555ca6b5 100644 --- a/website/docs/source/v2/getting-started/rebuild.html.md +++ b/website/docs/source/v2/getting-started/rebuild.html.md @@ -15,3 +15,6 @@ $ vagrant up That's it! Since the Vagrant environment is already all configured via the Vagrantfile, you or any of your coworkers simply have to run a `vagrant up` at any time and Vagrant will recreate your work environment. + +Teardown +Providers diff --git a/website/docs/source/v2/getting-started/share.html.md b/website/docs/source/v2/getting-started/share.html.md index 5c39d5de7..4d6c35d41 100644 --- a/website/docs/source/v2/getting-started/share.html.md +++ b/website/docs/source/v2/getting-started/share.html.md @@ -15,15 +15,15 @@ Vagrant Share lets you share your Vagrant environment to anyone around the world. It will give you a URL that will route directly to your Vagrant environment from any device in the world that is connected to the internet. -## Login to Vagrant Cloud +## Login to HashiCorp's Atlas Before being able to share your Vagrant environment, you'll need an account on -[Vagrant Cloud](https://vagrantcloud.com). Don't worry, it's free. +[HashiCorp's Atlas](https://atlas.hashicorp.com). Don't worry, it's free. Once you have an account, log in using `vagrant login`: ``` -$ vagrant login: +$ vagrant login Username or Email: mitchellh Password (will be hidden): You're now logged in! @@ -43,14 +43,17 @@ $ vagrant share Your URL will be different, so don't try the URL above. Instead, copy the URL that `vagrant share` outputted for you and visit that in a web -browser. It should load the index page we setup in the previous pages. +browser. It should load the Apache page we setup earlier. -Now, modify your "index.html" file and refresh the URL. It will be updated! -That URL is routing directly into your Vagrant environment, and works from -any device in the world that is connected to the internet. +If you modify the files in your shared folder and refresh the URL, you'll +see it update! The URL is routing directly into your Vagrant environment, +and works from any device in the world that is connected to the internet. To end the sharing session, hit `Ctrl+C` in your terminal. You can refresh the URL again to verify that your environment is no longer being shared. Vagrant Share is much more powerful than simply HTTP sharing. For more details, see the [complete Vagrant Share documentation](/v2/share/index.html). + +Networking +Teardown diff --git a/website/docs/source/v2/getting-started/synced_folders.html.md b/website/docs/source/v2/getting-started/synced_folders.html.md index 11612e84c..80c42c876 100644 --- a/website/docs/source/v2/getting-started/synced_folders.html.md +++ b/website/docs/source/v2/getting-started/synced_folders.html.md @@ -40,3 +40,6 @@ the folders in sync. With [synced folders](/v2/synced-folders/index.html), you can continue to use your own editor on your host machine and have the files sync into the guest machine. + +Up And SSH +Provisioning diff --git a/website/docs/source/v2/getting-started/teardown.html.md b/website/docs/source/v2/getting-started/teardown.html.md index 764de1969..04ce15f9f 100644 --- a/website/docs/source/v2/getting-started/teardown.html.md +++ b/website/docs/source/v2/getting-started/teardown.html.md @@ -23,19 +23,22 @@ work. The downside is that the virtual machine still eats up your disk space, and requires even more disk space to store all the state of the virtual machine RAM on disk. -**Halting** the virtual machine by calling `vagrant halt` will gracefully -shut down the guest operating system and power down the guest machine. -You can use `vagrant up` when you're ready to boot it again. The benefit of -this method is that it will cleanly shut down your machine, preserving the +**Halting** the virtual machine by calling `vagrant halt` will gracefully +shut down the guest operating system and power down the guest machine. +You can use `vagrant up` when you're ready to boot it again. The benefit of +this method is that it will cleanly shut down your machine, preserving the contents of disk, and allowing it to be cleanly started again. The downside is that it'll take some extra time to start from a cold boot, and the guest machine still consumes disk space. -**Destroying** the virtual machine by calling `vagrant destroy` will remove -all traces of the guest machine from your system. It'll stop the guest machine, -power it down, and remove all of the guest hard disks. Again, when you're ready to +**Destroying** the virtual machine by calling `vagrant destroy` will remove +all traces of the guest machine from your system. It'll stop the guest machine, +power it down, and remove all of the guest hard disks. Again, when you're ready to work again, just issue a `vagrant up`. The benefit of this is that _no cruft_ is left on your machine. The disk space and RAM consumed by the guest machine is reclaimed and your host machine is left clean. The downside is that `vagrant up` to get working again will take some extra time since it has to reimport the machine and reprovision it. + +Share +Rebuild diff --git a/website/docs/source/v2/getting-started/up.html.md b/website/docs/source/v2/getting-started/up.html.md index 3a9686afa..f1e59a6cf 100644 --- a/website/docs/source/v2/getting-started/up.html.md +++ b/website/docs/source/v2/getting-started/up.html.md @@ -33,3 +33,6 @@ virtual machine. Cool. When you're done fiddling around with the machine, run `vagrant destroy` back on your host machine, and Vagrant will remove all traces of the virtual machine. + +Boxes +Synced Folders diff --git a/website/docs/source/v2/hyperv/boxes.html.md b/website/docs/source/v2/hyperv/boxes.html.md index bf4afa9ea..4a0055ffe 100644 --- a/website/docs/source/v2/hyperv/boxes.html.md +++ b/website/docs/source/v2/hyperv/boxes.html.md @@ -91,3 +91,11 @@ $ tar cvzf ~/custom.box ./* A common mistake is to also package the parent folder by accident. Vagrant will not work in this case. To verify you've packaged it properly, add the box to Vagrant and try to bring up the machine. + +## Additional Help + +There is also some less structured help available from the experience of +other users. These aren't official documentation but if you're running +into trouble they may help you: + + * [Ubuntu 14.04.2 without secure boot](https://github.com/mitchellh/vagrant/issues/5419#issuecomment-86235427) diff --git a/website/docs/source/v2/hyperv/configuration.html.md b/website/docs/source/v2/hyperv/configuration.html.md index 2459c7c0d..b9f255181 100644 --- a/website/docs/source/v2/hyperv/configuration.html.md +++ b/website/docs/source/v2/hyperv/configuration.html.md @@ -8,6 +8,17 @@ sidebar_current: "hyperv-configuration" The Hyper-V provider has some provider-specific configuration options you may set. A complete reference is shown below: + * `vmname` (string) - Name of virtual mashine as shown in Hyper-V manager. + Defaults is taken from box image XML. + * `cpus` (integer) - Number of virtual CPU given to mashine. + Defaults is taken from box image XML. + * `memory` (integer) - Number of MegaBytes allocated to VM at startup. + Defaults is taken from box image XML. + * `maxmemory` (integer) - Number of MegaBytes maximal allowed to allocate for VM + This parameter is switch on Dynamic Allocation of memory. + Defaults is taken from box image XML. + * `vlan_id` (integer) - Number of Vlan ID for your guest network interface + Defaults is not defined, vlan configuration will be untouched if not set. * `ip_address_timeout` (integer) - The time in seconds to wait for the virtual machine to report an IP address. This defaults to 120 seconds. This may have to be increased if your VM takes longer to boot. diff --git a/website/docs/source/v2/hyperv/index.html.md b/website/docs/source/v2/hyperv/index.html.md index c2d31d0b5..6df335fc7 100644 --- a/website/docs/source/v2/hyperv/index.html.md +++ b/website/docs/source/v2/hyperv/index.html.md @@ -14,7 +14,8 @@ of Hyper-V do not include the necessary APIs for Vagrant to work. Hyper-V must be enabled prior to using the provider. Most Windows installations will not have Hyper-V enabled by default. To enable Hyper-V, go to -"Programs and Features" and check the box next to "Hyper-V." +"Programs and Features", click on "Turn Windows features on or off" and check +the box next to "Hyper-V."
Warning: Enabling Hyper-V will cause VirtualBox, VMware, diff --git a/website/docs/source/v2/hyperv/usage.html.md b/website/docs/source/v2/hyperv/usage.html.md index 7f3d9ccbe..5751fa511 100644 --- a/website/docs/source/v2/hyperv/usage.html.md +++ b/website/docs/source/v2/hyperv/usage.html.md @@ -17,5 +17,5 @@ admin rights. Vagrant will show you an error if it doesn't have the proper permissions. Boxes for Hyper-V can be easily found on -[Vagrant Cloud](https://vagrantcloud.com). To get started, you might +[HashiCorp's Atlas](https://atlas.hashicorp.com/boxes/search). To get started, you might want to try the `hashicorp/precise64` box. diff --git a/website/docs/source/v2/networking/forwarded_ports.html.md b/website/docs/source/v2/networking/forwarded_ports.html.md index 9c94bd530..f8ab3d2e8 100644 --- a/website/docs/source/v2/networking/forwarded_ports.html.md +++ b/website/docs/source/v2/networking/forwarded_ports.html.md @@ -50,7 +50,7 @@ there are more detailed examples of using these options. this is empty. * `protocol` (string) - Either "udp" or "tcp". This specifies the protocol - that will be allowed through the forwared port. By default this is "tcp". + that will be allowed through the forwarded port. By default this is "tcp". ## Forwarded Port Protocols diff --git a/website/docs/source/v2/networking/private_network.html.md b/website/docs/source/v2/networking/private_network.html.md index 7f7e77d2a..345547f05 100644 --- a/website/docs/source/v2/networking/private_network.html.md +++ b/website/docs/source/v2/networking/private_network.html.md @@ -63,6 +63,17 @@ the [reserved private address space](http://en.wikipedia.org/wiki/Private_networ and most routers actually block traffic from going to them from the outside world. +For some operating systems, additional configuration options for the static +IP address are available such as setting the default gateway or MTU. + +
+

+Warning! Do not choose an IP that overlaps with any +other IP space on your system. This can cause the network to not be +reachable. +

+
+ ## Disable Auto-Configuration If you want to manually configure the network interface yourself, you @@ -74,3 +85,7 @@ Vagrant.configure("2") do |config| auto_config: false end ``` + +If you already started the Vagrant environment before setting `auto_config`, +the files it initially placed there will stay there. You'll have to remove +those files manually or destroy and recreate the machine. diff --git a/website/docs/source/v2/networking/public_network.html.md b/website/docs/source/v2/networking/public_network.html.md index 81a650f53..e52cbaf58 100644 --- a/website/docs/source/v2/networking/public_network.html.md +++ b/website/docs/source/v2/networking/public_network.html.md @@ -23,6 +23,19 @@ general public access to your machine, public networks can.

+
+

+ Warning! Vagrant boxes are insecure by default + and by design, featuring public passwords, insecure keypairs + for SSH access, and potentially allow root access over SSH. With + these known credentials, your box is easily accessible by anyone on + your network. Before configuring Vagrant to use a public network, + consider all potential security implications + and review the default box + configuration to identify potential security risks. +

+
+ ## DHCP The easiest way to use a public network is to allow the IP to be assigned @@ -54,9 +67,80 @@ ask you to choose which interface the virtual machine should bridge to. A defaul interface can be specified by adding a `:bridge` clause to the network definition. ```ruby -config.vm.network "public_network", bridge: 'en1: Wi-Fi (AirPort)' +config.vm.network "public_network", bridge: "en1: Wi-Fi (AirPort)" ``` The string identifying the desired interface must exactly match the name of an available interface. If it can't be found, Vagrant will ask you to pick from a list of available network interfaces. + +With some providers, it is possible to specify a list of adapters to bridge +against: + +```ruby +config.vm.network "public_network", bridge: [ + "en1: Wi-Fi (AirPort)", + "en6: Broadcom NetXtreme Gigabit Ethernet Controller", +] +``` + +In this example, the first network adapter that exists and can successfully be +bridge will be used. + +## Disable Auto-Configuration + +If you want to manually configure the network interface yourself, you +can disable auto-configuration by specifying `auto_config`: + +```ruby +Vagrant.configure("2") do |config| + config.vm.network "public_network", auto_config: false +end +``` + +Then the shell provisioner can be used to configure the ip of the interface: + +```ruby +Vagrant.configure("2") do |config| + config.vm.network "public_network", auto_config: false + + # manual ip + config.vm.provision "shell", + run: "always", + inline: "ifconfig eth1 192.168.0.17 netmask 255.255.255.0 up" + + # manual ipv6 + config.vm.provision "shell", + run: "always", + inline: "ifconfig eth1 inet6 add fc00::17/7" +end +``` + +## Default Router + +Depending on your setup, you may wish to manually override the default +router configuration. This is required if you need access the Vagrant box from +other networks over the public network. To do so, you can use a shell +provisioner script: + +```ruby + config.vm.network "public_network", ip: "192.168.0.17" + + # default router + config.vm.provision "shell", + run: "always", + inline: "route add default gw 192.168.0.1" + + # default router ipv6 + config.vm.provision "shell", + run: "always", + inline: "route -A inet6 add default gw fc00::1 eth1" + + # delete default gw on eth0 + config.vm.provision "shell", + run: "always", + inline: "eval `route -n | awk '{ if ($8 ==\"eth0\" && $2 != \"0.0.0.0\") print \"route del default gw \" $2; }'`" +``` + +Note the above is fairly complex and may be guest OS specific, but we +document the rough idea of how to do it because it is a common question. diff --git a/website/docs/source/v2/other/environmental-variables.html.md b/website/docs/source/v2/other/environmental-variables.html.md index 9e0982cdc..41ecdd252 100644 --- a/website/docs/source/v2/other/environmental-variables.html.md +++ b/website/docs/source/v2/other/environmental-variables.html.md @@ -9,6 +9,22 @@ Vagrant has a set of environmental variables that can be used to configure and control it in a global way. This page lists those environmental variables. +## VAGRANT\_DEBUG\_LAUNCHER + +For performance reasons, especially for Windows users, Vagrant uses a static +binary to launch the actual Vagrant process. If you have _very_ early issues +when launching Vagrant from the official installer, you can specify the +`VAGRANT_DEBUG_LAUNCHER` environment variable to output debugging information +about the launch process. + +## VAGRANT\_CHECKPOINT\_DISABLE + +Vagrant does occasional network calls to check whether the version of Vagrant +that is running locally is up to date. We understand that software making remote +calls over the internet for any reason can be undesirable. To surpress these +calls, set the environment variable `VAGRANT_CHECKPOINT_DISABLE` to any +non-empty value. + ## VAGRANT\_CWD `VAGRANT_CWD` can be set to change the working directory of Vagrant. By @@ -74,6 +90,19 @@ Note that any `vagrant plugin` commands automatically don't load any plugins, so if you do install any unstable plugins, you can always use the `vagrant plugin` commands without having to worry. +## VAGRANT\_SKIP\_SUBPROCESS\_JAILBREAK + +As of Vagrant 1.7.3, Vagrant tries to intelligently detect if it is running in +the installer or running via Bundler. Although not officially supported, Vagrant +tries its best to work when executed via Bundler. When Vagrant detects that you +have spawned a subprocess that lives outside of Vagrant's installer, Vagrant +will do its best to reset the preserved environment dring the subprocess +execution. + +If Vagrant detects it is running outside of the officially installer, the +original environment will always be restored. You can disable this automatic +jailbreak by setting the `VAGRANT_SKIP_SUBPROCES_JAILBREAK`. + ## VAGRANT\_VAGRANTFILE This specifies the filename of the Vagrantfile that Vagrant searches for. diff --git a/website/docs/source/v2/plugins/hosts.html.md b/website/docs/source/v2/plugins/hosts.html.md index aaf1d6bdf..aed652f84 100644 --- a/website/docs/source/v2/plugins/hosts.html.md +++ b/website/docs/source/v2/plugins/hosts.html.md @@ -47,7 +47,7 @@ Implementations of hosts subclass `Vagrant.plugin("2", "host")`. Within this implementation, only the `detect?` method needs to be implemented. The `detect?` method is called by Vagrant very early on in its initialization -process to determine if the OS that Vagrant is running on is this hsot. +process to determine if the OS that Vagrant is running on is this host. If you detect that it is your operating system, return `true` from `detect?`. Otherwise, return `false`. diff --git a/website/docs/source/v2/providers/basic_usage.html.md b/website/docs/source/v2/providers/basic_usage.html.md index 8eb6dcf69..f266fcd8b 100644 --- a/website/docs/source/v2/providers/basic_usage.html.md +++ b/website/docs/source/v2/providers/basic_usage.html.md @@ -32,23 +32,22 @@ precise64 (vmware_fusion) ## Vagrant Up -Once a provider is installed, it is used by calling `vagrant up` with the `--provider` flag, -specifying the provider you want to back the machine. No other configuration -is necessary! What this looks like: +Once a provider is installed, you can use it by calling `vagrant up` +with the `--provider` flag. This will force Vagrant to use that specific +provider. No other configuration is necessary! + +In normal day-to-day usage, the `--provider` flag isn't necessary +since Vagrant can usually pick the right provider for you. More details +on how it does this is below. ``` $ vagrant up --provider=vmware_fusion ``` -If the provider is well-behaved then everything should just work. Of course, -each provider typically exposes custom configuration options to fine tune -and control that provider, but defaults should work great to get started. - -From this point forward, you can use all the other commands without -specifying a `--provider`; Vagrant is able to figure it out on its own. -Specifically, once you run `vagrant up --provider`, Vagrant is able to see -what provider is backing an existing machine, so commands such as `destroy`, -`suspend`, etc. do not need to be told what provider to use. +If you specified a `--provider` flag, you only need to do this for the +`up` command. Once a machine is up and running, Vagrant is able to +see what provider is backing a running machine, so commands such as +`destroy`, `suspend`, etc. do not need to be told what provider to use.

Limitations

@@ -65,3 +64,49 @@ what provider is backing an existing machine, so commands such as `destroy`, Vagrant.

+ +## Default Provider + +As mentioned earlier, you typically don't need to specify `--provider` +_ever_. Vagrant is smart enough about being able to detect the provider +you want for a given environment. + +Vagrant attempts to find the default provider in the following order: + + 1. The `--provider` flag on a `vagrant up` is chosen above all else, if + it is present. + + 2. If the `VAGRANT_DEFAULT_PROVIDER` environmental variable is set, + it takes next priority and will be the provider chosen. + + 3. Vagrant will go through all of the `config.vm.provider` calls in the + Vagrantfile and try each in order. It will choose the first provider + that is usable. For example, if you configure Hyper-V, it will never + be chosen on Mac this way. It must be both configured and usable. + + 4. Vagrant will go through all installed provider plugins (including the + ones that come with Vagrant), and find the first plugin that reports + it is usable. There is a priority system here: systems that are known + better have a higher priority than systems that are worse. For example, + if you have the VMware provider installed, it will always take priority + over VirtualBox. + + 5. If Vagrant still hasn't found any usable providers, it will error. + +Using this method, there are very few cases that Vagrant doesn't find the +correct provider for you. This also allows each +[Vagrantfile](/v2/vagrantfile/index.html) to define what providers +the development environment is made for by ordering provider configurations. + +A trick is to use `config.vm.provider` with no configuration at the top of +your Vagrantfile to define the order of providers you prefer to support: + +```ruby +Vagrant.configure("2") do |config| + # ... other config up here + + # Prefer VMware Fusion before VirtualBox + config.vm.provider "vmware_fusion" + config.vm.provider "virtualbox" +end +``` diff --git a/website/docs/source/v2/provisioning/ansible.html.md b/website/docs/source/v2/provisioning/ansible.html.md index 296185ef8..381cf5bd1 100644 --- a/website/docs/source/v2/provisioning/ansible.html.md +++ b/website/docs/source/v2/provisioning/ansible.html.md @@ -8,7 +8,7 @@ sidebar_current: "provisioning-ansible" **Provisioner name: `"ansible"`** The ansible provisioner allows you to provision the guest using -[Ansible](http://ansible.com) playbooks. +[Ansible](http://ansible.com) playbooks by executing `ansible-playbook` from the Vagrant host. Ansible playbooks are [YAML](http://en.wikipedia.org/wiki/YAML) documents that comprise the set of steps to be orchestrated on one or more machines. This documentation @@ -25,39 +25,44 @@ a single page of documentation.

+## Setup Requirements + +* [Install Ansible](http://docs.ansible.com/intro_installation.html#installing-the-control-machine) on your Vagrant host. +* Your Vagrant host should ideally provide a recent version of OpenSSH that [supports ControlPersist](http://docs.ansible.com/faq.html#how-do-i-get-ansible-to-reuse-connections-enable-kerberized-ssh-or-have-ansible-pay-attention-to-my-local-ssh-config-file) + ## Inventory File When using Ansible, it needs to know on which machines a given playbook should run. It does -this by way of an [inventory](http://docs.ansible.com/intro_inventory.html) file which lists those machines. In the context of Vagrant, -there are two ways to approach working with inventory files. +this by way of an [inventory](http://docs.ansible.com/intro_inventory.html) file which lists those machines. +In the context of Vagrant, there are two ways to approach working with inventory files. + +### Auto-Generated Inventory The first and simplest option is to not provide one to Vagrant at all. Vagrant will generate an -inventory file encompassing all of the virtual machine it manages, and use it for provisioning +inventory file encompassing all of the virtual machines it manages, and use it for provisioning machines. The generated inventory file is stored as part of your local Vagrant environment in `.vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory`. -The `ansible.groups` option can be used to pass a hash of group -names and group members to be included in the generated inventory file. Group variables -are intentionally not supported, as this practice is not recommended. -For example: +**Groups of Hosts** + +The `ansible.groups` option can be used to pass a hash of group names and group members to be included in the generated inventory file. + +With this configuration example: ``` ansible.groups = { "group1" => ["machine1"], - "group2" => ["machine2", "machine3"], - "all_groups:children" => ["group1", "group2", "group3"] + "group2" => ["machine2"], + "all_groups:children" => ["group1", "group2"] } ``` -Note that unmanaged machines and undefined groups are not added to the inventory. -For example, `group3` in the above example would not be added to the inventory file. - -A generated inventory might look like: +Vagrant would generate an inventory file that might look like: ``` # Generated by Vagrant -machine1 ansible_ssh_host=127.0.0.1 ansible_ssh_port=2200 -machine2 ansible_ssh_host=127.0.0.1 ansible_ssh_port=2201 +machine1 ansible_ssh_host=127.0.0.1 ansible_ssh_port=2200 ansible_ssh_private_key_file=/home/.../.vagrant/machines/machine1/virtualbox/private_key +machine2 ansible_ssh_host=127.0.0.1 ansible_ssh_port=2201 ansible_ssh_private_key_file=/home/.../.vagrant/machines/machine2/virtualbox/private_key [group1] machine1 @@ -70,6 +75,25 @@ group1 group2 ``` +**Notes** + + * The generation of group variables blocks (e.g. `[group1:vars]`) are intentionally not supported, as it is [not recommended to store group variables in the main inventory file](http://docs.ansible.com/intro_inventory.html#splitting-out-host-and-group-specific-data). A good practice is to store these group (or host) variables in `YAML` files stored in `group_vars/` or `host_vars/` directories in the playbook (or inventory) directory. + * Unmanaged machines and undefined groups are not added to the inventory, to avoid useless Ansible errors (e.g. *unreachable host* or *undefined child group*) + * Prior to Vagrant 1.7.3, the `ansible_ssh_private_key_file` variable was not set in generated inventory, but passed as command line argument to `ansible-playbook` command. + +For example, `machine3`, `group3` and `group1:vars` in the example below would not be added to the generated inventory file: + +``` +ansible.groups = { + "group1" => ["machine1"], + "group2" => ["machine2", "machine3"], + "all_groups:children" => ["group1", "group2", "group3"], + "group1:vars" => { "variable1" => 9, "variable2" => "example" } +} +``` + +### Static Inventory + The second option is for situations where you'd like to have more control over the inventory management. With the `ansible.inventory_path` option, you can reference a specific inventory resource (e.g. a static inventory file, a [dynamic inventory script](http://docs.ansible.com/intro_dynamic_inventory.html) or even [multiple inventories stored in the same directory](http://docs.ansible.com/intro_dynamic_inventory.html#using-multiple-inventory-sources)). Vagrant will then use this inventory information instead of generating it. @@ -85,8 +109,10 @@ Where the above IP address is one set in your Vagrantfile: config.vm.network :private_network, ip: "192.168.111.222" ``` -Note that machine names in `Vagrantfile` and `ansible.inventory_path` file should correspond, -unless you use `ansible.limit` option to reference the correct machines. +**Notes:** + + * The machine names in `Vagrantfile` and `ansible.inventory_path` files should correspond, unless you use `ansible.limit` option to reference the correct machines. + * The SSH host addresses (and ports) must obviously be specified twice, in `Vagrantfile` and `ansible.inventory_path` files. ## Playbook @@ -191,11 +217,9 @@ by the sudo command. * `ansible.tags` can be set to a string or an array of tags. Only plays, roles and tasks tagged with these values will be executed. * `ansible.skip_tags` can be set to a string or an array of tags. Only plays, roles and tasks that *do not match* these values will be executed. * `ansible.start_at_task` can be set to a string corresponding to the task name where the playbook provision will start. -* `ansible.raw_arguments` can be set to an array of strings corresponding to a list of `ansible-playbook` arguments (e.g. `['--check', '-M /my/modules']`). It is an *unsafe wildcard* that can be used to apply Ansible options that are not (yet) supported by this Vagrant provisioner. Following precedence rules apply: - * Any supported options (described above) will override conflicting `raw_arguments` value (e.g. `--tags` or `--start-at-task`) - * Vagrant default user authentication can be overridden via `raw_arguments` (with custom values for `--user` and `--private-key`) +* `ansible.raw_arguments` can be set to an array of strings corresponding to a list of `ansible-playbook` arguments (e.g. `['--check', '-M /my/modules']`). It is an *unsafe wildcard* that can be used to apply Ansible options that are not (yet) supported by this Vagrant provisioner. As of Vagrant 1.7, `raw_arguments` has the highest priority and its values can potentially override or break other Vagrant settings. * `ansible.raw_ssh_args` can be set to an array of strings corresponding to a list of OpenSSH client parameters (e.g. `['-o ControlMaster=no']`). It is an *unsafe wildcard* that can be used to pass additional SSH settings to Ansible via `ANSIBLE_SSH_ARGS` environment variable. -* `ansible.host_key_checking` can be set to `true` which will enable host key checking. As Vagrant 1.5, the default value is `false`, to avoid connection problems when creating new virtual machines. +* `ansible.host_key_checking` can be set to `true` which will enable host key checking. As of Vagrant 1.5, the default value is `false` and as of Vagrant 1.7 the user known host file (e.g. `~/.ssh/known_hosts`) is no longer read nor modified. In other words: by default, the Ansible provisioner behaves the same as Vagrant native commands (e.g `vagrant ssh`). ## Tips and Tricks @@ -204,24 +228,43 @@ by the sudo command. Vagrant is designed to provision [multi-machine environments](/v2/multi-machine) in sequence, but the following configuration pattern can be used to take advantage of Ansible parallelism: ``` - config.vm.define 'machine2' do |machine| - machine.vm.hostname = 'machine2' - machine.vm.network "private_network", ip: "192.168.77.22" - end +# Vagrant 1.7+ automatically inserts a different +# insecure keypair for each new VM created. The easiest way +# to use the same keypair for all the machines is to disable +# this feature and rely on the legacy insecure key. +# config.ssh.insert_key = false +# +# Note: +# As of Vagrant 1.7.3, it is no longer necessary to disable +# the keypair creation when using the auto-generated inventory. - config.vm.define 'machine1' do |machine| - machine.vm.hostname = 'machine1' - machine.vm.network "private_network", ip: "192.168.77.21" +N = 3 +(1..N).each do |machine_id| + config.vm.define "machine#{machine_id}" do |machine| - machine.vm.provision :ansible do |ansible| - ansible.playbook = "playbook.yml" + machine.vm.hostname = "machine#{machine_id}" + machine.vm.network "private_network", ip: "192.168.77.#{20+machine_id}" - # Disable default limit (required with Vagrant 1.5+) - ansible.limit = 'all' + # Only execute once the Ansible provisioner, + # when all the machines are up and ready. + if machine_id == N + machine.vm.provision :ansible do |ansible| + + # Disable default limit to connect to all the machines + ansible.limit = 'all' + ansible.playbook = "playbook.yml" + + end end + end +end ``` +**Caveats:** + +If you apply this parallel provisioning pattern with a static Ansible inventory, you'll have to organize the things so that [all the relevant private keys are provided to the `ansible-playbook` command](https://github.com/mitchellh/vagrant/pull/5765#issuecomment-120247738). The same kind of considerations applies if you are using multiple private keys for a same machine (see [`config.ssh.private_key_path` SSH setting](/v2/vagrantfile/ssh_settings.html)). + ### Provide a local `ansible.cfg` file Certain settings in Ansible are (only) adjustable via a [configuration file](http://docs.ansible.com/intro_configuration.html), and you might want to ship such a file in your Vagrant project. @@ -232,7 +275,7 @@ Note that it is also possible to reference an Ansible configuration file via `AN ### Why does the Ansible provisioner connect as the wrong user? -It is good to know that following Ansible settings always override the `config.ssh.username` option defined in [Vagrant SSH Settings](/v2/vagrantfile/ssh_settings.html): +It is good to know that the following Ansible settings always override the `config.ssh.username` option defined in [Vagrant SSH Settings](/v2/vagrantfile/ssh_settings.html): * `ansible_ssh_user` variable * `remote_user` (or `user`) play attribute @@ -254,3 +297,26 @@ In a situation like the above, to override the `remote_user` specified in a play ``` ansible.extra_vars = { ansible_ssh_user: 'vagrant' } ``` + +### Force Paramiko Connection Mode + +The Ansible provisioner is implemented with native OpenSSH support in mind, and there is no official support for [paramiko](https://github.com/paramiko/paramiko/) (A native Python SSHv2 protocol library). + +If you really need to use this connection mode, it is though possible to enable paramiko as illustrated in the following configuration examples: + + +With auto-generated inventory: + +``` +ansible.raw_arguments = ["--connection=paramiko"] +``` + +With a custom inventory, the private key must be specified (e.g. via an `ansible.cfg` configuration file, `--private-key` argument, or as part of your inventory file): + +``` +ansible.inventory_path = "./my-inventory" +ansible.raw_arguments = [ + "--connection=paramiko", + "--private-key=/home/.../.vagrant/machines/.../private_key" +] +``` \ No newline at end of file diff --git a/website/docs/source/v2/provisioning/basic_usage.html.md b/website/docs/source/v2/provisioning/basic_usage.html.md index 659f6aa40..40281c7e9 100644 --- a/website/docs/source/v2/provisioning/basic_usage.html.md +++ b/website/docs/source/v2/provisioning/basic_usage.html.md @@ -11,7 +11,8 @@ points common to all provisioners that are important to know. ## Configuration -First, every provisioner is configured within your [Vagrantfile](/v2/vagrantfile/index.html) +First, every provisioner is configured within your +[Vagrantfile](/v2/vagrantfile/index.html) using the `config.vm.provision` method call. For example, the Vagrantfile below enables shell provisioning: @@ -44,6 +45,29 @@ it can greatly improve readability. Additionally, some provisioners, like the Chef provisioner, have special methods that can be called within that block to ease configuration that can't be done with the key/value approach. +The attributes that can be set in a single-line are the attributes that +are set with the `=` style, such as `inline = "echo hello"` above. If the +style is instead more of a function call, such as `add_recipe "foo"`, then +this can't be specified in a single line. + +Provisioners can also be named (since 1.7.0). These names are used cosmetically for output +as well as overriding provisioner settings (covered further below). An example +of naming provisioners is shown below: + +```ruby +Vagrant.configure("2") do |config| + # ... other configuration + + config.vm.provision "bootstrap", type: "shell" do |s| + s.inline = "echo hello" + end +end +``` + +Naming provisioners is simple. The first argument to `config.vm.provision` +becomes the name, and then a `type` option is used to specify the provisioner +type, such as `type: "shell"` above. + ## Running Provisioners Provisioners are run in three cases: the initial `vagrant up`, `vagrant @@ -57,6 +81,8 @@ The `--provision-with` flag can be used if you only want to run a specific provisioner if you have multiple provisioners specified. For example, if you have a shell and Puppet provisioner and only want to run the shell one, you can do `vagrant provision --provision-with shell`. +The arguments to `--provision-with` can be the provisioner type (such as +"shell") or the provisioner name (such as "bootstrap" from above). ## Run Once or Always @@ -118,6 +144,9 @@ The ordering of the provisioners will be to echo "foo", "baz", then "bar" (note the second one might not be what you expect!). Remember: ordering is _outside in_. +With multiple provisioners, use the `--provision-with` setting along +with names to get more fine grainted control over what is run and when. + ## Overriding Provisioner Settings
@@ -135,17 +164,16 @@ you may want to define common provisioners in the global configuration scope of a Vagrantfile, but override certain aspects of them internally. Vagrant allows you to do this, but has some details to consider. -To override settings, you must assign an ID to your provisioner. Then -it is only a matter of specifying the same ID to override: +To override settings, you must assign a name to your provisioner. ```ruby Vagrant.configure("2") do |config| - config.vm.provision "shell", - inline: "echo foo", id: "foo" + config.vm.provision "foo", type: "shell", + inline: "echo foo" config.vm.define "web" do |web| - web.vm.provision "shell", - inline: "echo bar", id: "foo" + web.vm.provision "foo", type: "shell", + inline: "echo bar" end end ``` @@ -162,14 +190,14 @@ below, the output would be "foo" then "bar": ```ruby Vagrant.configure("2") do |config| - config.vm.provision "shell", - inline: "echo ORIGINAL!", id: "foo" + config.vm.provision "foo", type: "shell", + inline: "echo ORIGINAL!" config.vm.define "web" do |web| web.vm.provision "shell", inline: "echo foo" - web.vm.provision "shell", - inline: "echo bar", id: "foo" + web.vm.provision "foo", type: "shell", + inline: "echo bar" end end ``` diff --git a/website/docs/source/v2/provisioning/chef_apply.html.md b/website/docs/source/v2/provisioning/chef_apply.html.md new file mode 100644 index 000000000..a5d53c564 --- /dev/null +++ b/website/docs/source/v2/provisioning/chef_apply.html.md @@ -0,0 +1,102 @@ +--- +page_title: "Chef Apply - Provisioning" +sidebar_current: "provisioning-chefapply" +--- + +# Chef Apply Provisioner + +**Provisioner name: `chef_apply`** + +The Chef Apply provisioner allows you to provision the guest using +[Chef](https://www.getchef.com/), specifically with +[Chef Apply](https://docs.getchef.com/ctl_chef_apply.html). + +Chef Apply is ideal for people who are already experienced with Chef and the +Chef ecosystem. Specifically, this documentation page does not cover how use +Chef or how to write Chef recipes. + +
+

+ Warning: If you are not familiar with Chef and Vagrant already, + we recommend starting with the shell + provisioner. +

+
+ +## Options + +This section lists the complete set of available options for the Chef Apply +provisioner. More detailed examples of how to use the provisioner are +available below this section. + +* `recipe` (string) - The raw recipe contents to execute using Chef Apply on + the guest. + +* `log_level` (string) - The log level to use while executing `chef-apply`. The + default value is "info". + +* `upload_path` (string) - **Advanced!** The location on the guest where the + generated recipe file should be stored. For most use cases, it is unlikely you + will need to customize this value. The default value is + `/tmp/vagrant-chef-apply-#` where `#` is a unique counter generated by + Vagrant to prevent collisions. + +In addition to all the options listed above, the Chef Apply provisioner supports +the [common options for all Chef provisioners](/v2/provisioning/chef_common.html). + +## Specifying a Recipe + +The easiest way to get started with the Chef Apply provisioner is to just +specify an inline +[Chef recipe](http://docs.chef.io/recipes.html). For +example: + +```ruby +Vagrant.configure("2") do |config| + config.vm.provision "chef_apply" do |chef| + chef.recipe = "package[apache2]" + end +end +``` + +This causes Vagrant to run Chef Apply with the given recipe contents. If you are +familiar with Chef, you know this will install the apache2 package from the +system package provider. + +Since single-line Chef recipes are rare, you can also specify the recipe using a +"heredoc": + +```ruby +Vagrant.configure("2") do |config| + config.vm.provision "chef_apply" do |chef| + chef.recipe = <<-RECIPE + package "apache2" + + template "/etc/apache2/my.config" do + # ... + end + RECIPE + end +end +``` + +Finally, if you would prefer to store the recipe as plain-text, you can set the +recipe to the contents of a file: + +```ruby +Vagrant.configure("2") do |config| + config.vm.provision "chef_apply" do |chef| + chef.recipe = File.read("/path/to/my/recipe.rb") + end +end +``` + +## Roles + +The Vagrant Chef Apply provisioner does not support roles. Please use the a +different Vagrant Chef provisioner if you need support for roles. + +## Data Bags + +The Vagrant Chef Apply provisioner does not support data_bags. Please use the a +different Vagrant Chef provisioner if you need support for data_bags. diff --git a/website/docs/source/v2/provisioning/chef_client.html.md b/website/docs/source/v2/provisioning/chef_client.html.md index a22ddcc47..48bfd34f7 100644 --- a/website/docs/source/v2/provisioning/chef_client.html.md +++ b/website/docs/source/v2/provisioning/chef_client.html.md @@ -8,7 +8,7 @@ sidebar_current: "provisioning-chefclient" **Provisioner name: `chef_client`** The Chef Client provisioner allows you to provision the guest using -[Chef](http://www.opscode.com/chef/), specifically by connecting +[Chef](http://www.chef.io/chef/), specifically by connecting to an existing Chef Server and registering the Vagrant machine as a node within your infrastructure. @@ -65,7 +65,7 @@ server itself. ## Environments -You can specify the [environment](http://wiki.opscode.com/display/chef/Environments) +You can specify the [environment](http://docs.chef.io/environments.html) for the node to come up in using the `environment` configuration option: ```ruby @@ -82,12 +82,15 @@ end There are a few more configuration options available. These generally don't need to be modified but are available if your Chef Server requires customization -of these variables: +of these variables. * `client_key_path` * `node_name` * `validation_client_name` +In addition to all the options listed above, the Chef Client provisioner supports +the [common options for all Chef provisioners](/v2/provisioning/chef_common.html). + ## Cleanup When you provision your Vagrant virtual machine with Chef Server, it creates a diff --git a/website/docs/source/v2/provisioning/chef_common.html.md b/website/docs/source/v2/provisioning/chef_common.html.md index 1a41691ed..b5347d522 100644 --- a/website/docs/source/v2/provisioning/chef_common.html.md +++ b/website/docs/source/v2/provisioning/chef_common.html.md @@ -5,12 +5,55 @@ sidebar_current: "provisioning-chefcommon" # Shared Chef Options -This page documents the list of available options that are available in -both the -[Chef solo](/v2/provisioning/chef_solo.html) -and -[Chef client](/v2/provisioning/chef_client.html) -provisioners. +## All Chef Provisioners + +The following options are available to all Chef provisioners. Many of these +options are for advanced users only and should not be used unless you understand +their purpose. + +- `binary_path` (string) - The path to Chef's `bin/` directory on the guest + machine. + +- `binary_env` (string) - Arbitrary environment variables to set before running + the Chef provisioner command. This should be of the format `KEY=value` as a + string. + +- `install` (boolean, string) - Install Chef on the system if it does not exist. + The default value is "true", which will use the official Omnibus installer + from Chef. This is a trinary attribute (it can have three values): + + - `true` (boolean) - install Chef + - `false` (boolean) - do not install Chef + - `"force"` (string) - install Chef, even if it is already installed at the + proper version on the guest + +- `installer_download_path` (string) - The path where the Chef installer will be + downloaded to. This option is only honored if the `install` attribute is + `true` or `"force"`. The default value is to use the path provided by Chef's + Omnibus installer, which varies between releases. + +- `log_level` (string) - The Chef log level. See the Chef docs for acceptable + values. + +- `prerelease` (boolean) - Install a prerelease version of Chef. The default + value is false. + +- `version` (string) - The version of Chef to install on the guest. If Chef is + already installed on the system, the installed version is compared with the + requested version. If they match, no action is taken. If they do not match, + the value specified in this attribute will be installed in favor of the + existing version (a message will be displayed). + + You can also specify "latest" (default), which will install the latest + version of Chef on the system. In this case, Chef will use whatever + version is on the system. To force the newest version of Chef to be + installed on every provision, set the {#install} option to "force". + + +## Runner Chef Provisioners + +The following options are available to any of the Chef "runner" provisioners +which include [Chef Solo](/v2/provisioning/chef_solo.html), [Chef Zero](/v2/provisioning/chef_zero.html), and [Chef Client](/v2/provisioning/chef_client.html). * `arguments` (string) - A list of additional arguments to pass on the command-line to Chef. Since these are passed in a shell-like environment, @@ -21,9 +64,6 @@ provisioners. This defaults to 1. This can be increased to a higher number if your Chef runs take multiple runs to reach convergence. -* `binary_path` (string) - The path to the directory of the Chef executable - binaries. By default, Vagrant looks for the proper Chef binary on the PATH. - * `custom_config_path` (string) - A path to a custom Chef configuration local on your machine that will be used as the Chef configuration. This Chef configuration will be loaded _after_ the Chef configuration that Vagrant @@ -65,3 +105,6 @@ provisioners. * `verbose_logging` (boolean) - Whether or not to enable the Chef `verbose_logging` option. By default this is false. + +* `enable_reporting` (boolean) - Whether or not to enable the Chef + `enable_reporting` option. By default this is true. diff --git a/website/docs/source/v2/provisioning/chef_solo.html.md b/website/docs/source/v2/provisioning/chef_solo.html.md index f20cf33bd..a7841b90e 100644 --- a/website/docs/source/v2/provisioning/chef_solo.html.md +++ b/website/docs/source/v2/provisioning/chef_solo.html.md @@ -8,8 +8,8 @@ sidebar_current: "provisioning-chefsolo" **Provisioner name: `chef_solo`** The Chef Solo provisioner allows you to provision the guest using -[Chef](http://www.opscode.com/chef/), specifically with -[Chef Solo](http://docs.opscode.com/chef_solo.html). +[Chef](https://www.chef.io/chef/), specifically with +[Chef Solo](http://docs.chef.io/chef_solo.html). Chef Solo is ideal for people who are already experienced with Chef, already have Chef cookbooks, or are looking to learn Chef. Specifically, @@ -32,10 +32,6 @@ This section lists the complete set of available options for the Chef Solo provisioner. More detailed examples of how to use the provisioner are available below this section. -Note that only the Chef-solo specific options are shown below. There is -also a large set of [common options](/v2/provisioning/chef_common.html) -that are available with both the Chef Solo and Chef client provisioners. - * `cookbooks_path` (string or array) - A list of paths to where cookbooks are stored. By default this is "cookbooks", expecting a cookbooks folder relative to the Vagrantfile location. @@ -62,10 +58,13 @@ that are available with both the Chef Solo and Chef client provisioners. this will use the default synced folder type. For example, you can set this to "nfs" to use NFS synced folders. +In addition to all the options listed above, the Chef Solo provisioner supports +the [common options for all Chef provisioners](/v2/provisioning/chef_common.html). + ## Specifying a Run List The easiest way to get started with the Chef Solo provisioner is to just -specify a [run list](http://docs.opscode.com/essentials_node_object_run_lists.html). This looks like: +specify a [run list](https://docs.chef.io/nodes.html#about-run-lists). This looks like: ```ruby Vagrant.configure("2") do |config| @@ -89,6 +88,9 @@ $ tree |   |-- default.rb ``` +The order of the calls to `add_recipe` will specify the order of the run list. +Earlier recipes added with `add_recipe` are run before later recipes added. + ## Custom Cookbooks Path Instead of using the default "cookbooks" directory, a custom cookbooks @@ -117,7 +119,7 @@ end ## Roles -Vagrant also supports provisioning with [Chef roles](http://docs.opscode.com/essentials_roles.html). +Vagrant also supports provisioning with [Chef roles](http://docs.chef.io/roles.html). This is done by specifying a path to a roles folder where roles are defined and by adding roles to your run list: @@ -143,7 +145,7 @@ Vagrant. ## Data Bags -[Data bags](http://docs.opscode.com/essentials_data_bags.html) are also +[Data bags](http://docs.chef.io/data_bags.html) are also supported by the Chef Solo provisioner. This is done by specifying a path to your data bags directory: diff --git a/website/docs/source/v2/provisioning/chef_zero.html.md b/website/docs/source/v2/provisioning/chef_zero.html.md new file mode 100644 index 000000000..4f35f362a --- /dev/null +++ b/website/docs/source/v2/provisioning/chef_zero.html.md @@ -0,0 +1,84 @@ +--- +page_title: "Chef Zero - Provisioning" +sidebar_current: "provisioning-chefzero" +--- + +# Chef Zero Provisioner + +**Provisioner name: `chef_zero`** + +The Chef Zero provisioner allows you to provision the guest using +[Chef](https://www.getchef.com/chef/), specifically with +[Chef Zero/local mode](https://docs.getchef.com/ctl_chef_client.html#run-in-local-mode). + +This new provisioner is a middle ground between running a full blown +Chef Server and using the limited [Chef Solo](/v2/provisioning/chef_solo.html) +provisioner. It runs a local in-memory Chef Server and fakes the validation +and client key registration. + +
+

+ Warning: If you're not familiar with Chef and Vagrant already, + I recommend starting with the shell + provisioner. However, if you're comfortable with Vagrant already, Vagrant + is the best way to learn Chef. +

+
+ +## Options + +This section lists the complete set of available options for the Chef Zero +provisioner. More detailed examples of how to use the provisioner are +available below this section. + +* `cookbooks_path` (string or array) - A list of paths to where cookbooks + are stored. By default this is "cookbooks", expecting a cookbooks folder + relative to the Vagrantfile location. + +* `data_bags_path` (string) - A path where data bags are stored. By default, no + data bag path is set. + +* `environments_path` (string) - A path where environment definitions are + located. By default, no environments folder is set. + +* `environment` (string) - The environment you want the Chef run to be + a part of. This requires Chef 11.6.0 or later, and that `environments_path` + is set. + +* `roles_path` (string or array) - A list of paths where roles are defined. + By default this is empty. Multiple role directories are only supported by + Chef 11.8.0 and later. + +* `synced_folder_type` (string) - The type of synced folders to use when + sharing the data required for the provisioner to work properly. By default + this will use the default synced folder type. For example, you can set this + to "nfs" to use NFS synced folders. + + +In addition to all the options listed above, the Chef Zero provisioner supports +the [common options for all Chef provisioners](/v2/provisioning/chef_common.html). + +## Usage + +The Chef Zero provisioner is configured basically the same way as the Chef Solo +provisioner. See the [Chef Solo documentations](/v2/provisioning/chef_solo.html) +for more information. + +A basic example could look like this: + +```ruby +Vagrant.configure("2") do |config| + config.vm.provision "chef_zero" do |chef| + # Specify the local paths where Chef data is stored + chef.cookbooks_path = "cookbooks" + chef.data_bags_path = "data_bags" + chef.roles_path = "roles" + + # Add a recipe + chef.add_recipe "apache" + + # Or maybe a role + chef.add_role "web" + end +end +``` diff --git a/website/docs/source/v2/provisioning/docker.html.md b/website/docs/source/v2/provisioning/docker.html.md index daa9651f4..0e03ec660 100644 --- a/website/docs/source/v2/provisioning/docker.html.md +++ b/website/docs/source/v2/provisioning/docker.html.md @@ -53,7 +53,8 @@ of these functions have examples in more detailed sections below. * `pull_images` - Pull the given images. This does not start these images. -* `run` - Run a container and configure it to start on boot. +* `run` - Run a container and configure it to start on boot. This can + only be specified once. ## Building Images @@ -155,6 +156,9 @@ In addition to the name, the `run` method accepts a set of options, all optional * `daemonize` (boolean) - If true, the "-d" flag is given to `docker run` to daemonize the containers. By default this is true. +* `restart` (string) - The restart policy for the container. Defaults to + "always" + For example, here is how you would configure Docker to run a container with the Vagrant shared directory mounted inside of it: diff --git a/website/docs/source/v2/provisioning/puppet_apply.html.md b/website/docs/source/v2/provisioning/puppet_apply.html.md index b033bcd5e..89fe90195 100644 --- a/website/docs/source/v2/provisioning/puppet_apply.html.md +++ b/website/docs/source/v2/provisioning/puppet_apply.html.md @@ -26,6 +26,8 @@ This section lists the complete set of available options for the Puppet provisioner. More detailed examples of how to use the provisioner are available below this section. +* `binary_path` (string) - Path on the guest to Puppet's `bin/` directory. + * `facter` (hash) - A hash of data to set as available facter variables within the Puppet run. @@ -43,6 +45,11 @@ available below this section. * `module_path` (string) - Path, on the host, to the directory which contains Puppet modules, if any. +* `environment` (string) - Name of the Puppet environment. + +* `environment_path` (string) - Path to the directory that contains environment + files on the host disk. + * `options` (array of strings) - Additionally options to pass to the Puppet executable when running Puppet. @@ -51,6 +58,10 @@ available below this section. this will use the default synced folder type. For example, you can set this to "nfs" to use NFS synced folders. +* `synced_folder_args` (array) - Arguments that are passed to the folder sync. + For example ['-a', '--delete', '--exclude=fixtures'] for the rsync sync + command. + * `temp_dir` (string) - The directory where all the data associated with the Puppet run (manifest files, modules, etc.) will be stored on the guest machine. @@ -59,6 +70,13 @@ available below this section. directory when Puppet is executed. This is usually only set because relative paths are used in the Hiera configuration. +~> If only `environment` and `environments_path` are specified, it will parse +and use the manifest specified in the `environment.conf` file. If +`manifests_path` and `manifest_file` is specified along with the environment +options, the manifest from the environment will be overridden by the specified `manifest_file`. If `manifests_path` and `manifest_file` are specified without +environments, the old non-environment mode will be used (which will fail on +Puppet 4+). + ## Bare Minimum The quickest way to get started with the Puppet provisioner is to just @@ -120,6 +138,20 @@ end It is a somewhat odd syntax, but the tuple (two-element array) says that the path is located in the "vm" at "/path/to/manifests". +## Environments + +If you are using Puppet 4 or higher, you can also specify the name of the +Puppet environment and the path on the local disk to the environment files: + +```ruby +Vagrant.configure("2") do |config| + config.vm.provision "puppet" do |puppet| + puppet.environment_path = "../puppet/environments" + puppet.environment = "testenv" + end +end +``` + ## Modules Vagrant also supports provisioning with [Puppet modules](http://docs.puppetlabs.com/guides/modules.html). diff --git a/website/docs/source/v2/provisioning/salt.html.md b/website/docs/source/v2/provisioning/salt.html.md index f66ac7c60..7dbd9777f 100644 --- a/website/docs/source/v2/provisioning/salt.html.md +++ b/website/docs/source/v2/provisioning/salt.html.md @@ -56,15 +56,25 @@ on this machine. Not supported on Windows. `false`. Not supported on Windows. * `install_type` (stable | git | daily | testing) - Whether to install from a -distribution's stable package manager, git tree-ish, daily ppa, or testing repository. +distribution's stable package manager, git tree-ish, daily ppa, or testing repository. Not supported on Windows. * `install_args` (develop) - When performing a git install, -you can specify a branch, tag, or any treeish. Not supported on Windows. +you can specify a branch, tag, or any treeish. If using the `custom` install type, +you can also specify a different repository to install from. +Not supported on Windows. + +* `install_command` (string) - Allow specifying an arbitrary string of arguments +to the bootstrap script. This will completely ignore `install_type` and `install_args` +to allow more flexibility with the bootstrap process. * `always_install` (boolean) - Installs salt binaries even if they are already detected, default `false` +* `bootstrap_script` (string) - Path to your customized salt-bootstrap.sh script. + +* `bootstrap_options` (string) - Additional command-line options to + pass to the bootstrap script. ## Minion Options These only make sense when `no_minion` is `false`. @@ -77,11 +87,12 @@ a custom salt minion config file. * `minion_pub` (salt/key/minion.pub) - Path to your minion public key +* `grains_config` (string) - Path to a custom salt grains file. ## Master Options These only make sense when `install_master` is `true`. -* `master_config` (string, default: "salt/minion") +* `master_config` (string, default: "salt/master") Path to a custom salt master config file * `master_key` (salt/key/master.pem) - Path to your master key diff --git a/website/docs/source/v2/provisioning/shell.html.md b/website/docs/source/v2/provisioning/shell.html.md index db782aff9..978d8cbda 100644 --- a/website/docs/source/v2/provisioning/shell.html.md +++ b/website/docs/source/v2/provisioning/shell.html.md @@ -59,6 +59,12 @@ The remainder of the available options are optional: true, Vagrant will not do this, allowing the native colors from the script to be outputted. +* `name` (string) - This value will be displayed in the output so that + identification by the user is easier when many shell provisioners are present. + +* `powershell_args` (string) - Extra arguments to pass to `PowerShell` + if you're provisioning with PowerShell on Windows. + ## Inline Scripts @@ -133,6 +139,16 @@ that the external path has the proper extension (".bat" or ".ps1"), because Windows uses this to determine what kind fo file it is to execute. If you exclude this extension, it likely won't work. +To run a script already available on the guest you can use an inline script to +invoke the remote script on the guest. + +```ruby +Vagrant.configure("2") do |config| + config.vm.provision "shell", + inline: "/bin/sh /path/to/the/script/already/on/the/guest.sh" +end +``` + ## Script Arguments You can parameterize your scripts as well like any normal shell script. diff --git a/website/docs/source/v2/push/atlas.html.md b/website/docs/source/v2/push/atlas.html.md new file mode 100644 index 000000000..e5679bbf2 --- /dev/null +++ b/website/docs/source/v2/push/atlas.html.md @@ -0,0 +1,67 @@ +--- +page_title: "Vagrant Push - Atlas Strategy" +sidebar_current: "push-atlas" +description: |- + Atlas is HashiCorp's commercial offering to bring your Vagrant development + environments to production. The Vagrant Push Atlas strategy pushes your + application's code to HashiCorp's Atlas service. +--- + +# Vagrant Push + +## Atlas Strategy + +[Atlas][] is HashiCorp's commercial offering to bring your Vagrant development +environments to production. You can read more about HashiCorp's Atlas and all +its features on [the Atlas homepage][Atlas]. The Vagrant Push Atlas strategy +pushes your application's code to HashiCorp's Atlas service. + +The Vagrant Push Atlas strategy supports the following configuration options: + +- `app` - The name of the application in [HashiCorp's Atlas][Atlas]. If the + application does not exist, it will be created with user confirmation. + +- `exclude` - Add a file or file pattern to exclude from the upload, relative to + the `dir`. This value may be specified multiple times and is additive. + `exclude` take precedence over `include` values. + +- `include` - Add a file or file pattern to include in the upload, relative to + the `dir`. This value may be specified multiple times and is additive. + +- `dir` - The base directory containing the files to upload. By default this is + the same directory as the Vagrantfile, but you can specify this if you have + a `src` folder or `bin` folder or some other folder you want to upload. + +- `vcs` - If set to true, Vagrant will automatically use VCS data to determine + the files to upload. Uncommitted changes will not be deployed. + +Additionally, the following options are exposed for power users of the Vagrant +Atlas push strategy. Most users will not require these options: + +- `address` - The address of the Atlas server to upload to. By default this will + be the public Atlas server. + +- `token` - The Atlas token to use. If the user has run `vagrant login`, this + will the token generated by that command. If the environment variable + `ATLAS_TOKEN` is set, the uploader will use this value. By default, this is + nil. + + +### Usage + +The Vagrant Push Atlas strategy is defined in the `Vagrantfile` using the +`atlas` key: + +```ruby +config.push.define "atlas" do |push| + push.app = "username/application" +end +``` + +And then push the application to Atlas: + +```shell +$ vagrant push +``` + +[Atlas]: https://atlas.hashicorp.com/ "HashiCorp's Atlas Service" diff --git a/website/docs/source/v2/push/ftp.html.md b/website/docs/source/v2/push/ftp.html.md new file mode 100644 index 000000000..54a2e1427 --- /dev/null +++ b/website/docs/source/v2/push/ftp.html.md @@ -0,0 +1,62 @@ +--- +page_title: "Vagrant Push - FTP & SFTP Strategy" +sidebar_current: "push-ftp" +description: |- + +--- + +# Vagrant Push + +## FTP & SFTP Strategy + +Vagrant Push FTP and SFTP strategy pushes the code in your Vagrant development +environment to a remote FTP or SFTP server. + +The Vagrant Push FTP And SFTP strategy supports the following configuration +options: + +- `host` - The address of the remote (S)FTP server. If the (S)FTP server is + running on a non-standard port, you can specify the port after the address + (`host:port`). + +- `username` - The username to use for authentication with the (S)FTP server. + +- `password` - The password to use for authentication with the (S)FTP server. + +- `passive` - Use passive FTP (default is true). + +- `secure` - Use secure (SFTP) (default is false). + +- `destination` - The root destination on the target system to sync the files + (default is `/`). + +- `exclude` - Add a file or file pattern to exclude from the upload, relative to + the `dir`. This value may be specified multiple times and is additive. + `exclude` take precedence over `include` values. + +- `include` - Add a file or file pattern to include in the upload, relative to + the `dir`. This value may be specified multiple times and is additive. + +- `dir` - The base directory containing the files to upload. By default this is + the same directory as the Vagrantfile, but you can specify this if you have + a `src` folder or `bin` folder or some other folder you want to upload. + + +### Usage + +The Vagrant Push FTP and SFTP strategy is defined in the `Vagrantfile` using the +`ftp` key: + +```ruby +config.push.define "ftp" do |push| + push.host = "ftp.company.com" + push.username = "username" + push.password = "password" +end +``` + +And then push the application to the FTP or SFTP server: + +```shell +$ vagrant push +``` diff --git a/website/docs/source/v2/push/heroku.html.md b/website/docs/source/v2/push/heroku.html.md new file mode 100644 index 000000000..90aa20e82 --- /dev/null +++ b/website/docs/source/v2/push/heroku.html.md @@ -0,0 +1,63 @@ +--- +page_title: "Vagrant Push - Heroku Strategy" +sidebar_current: "push-heroku" +description: |- + The Vagrant Push Heroku strategy pushes your application's code to Heroku. + Only files which are committed to the Git repository are pushed to Heroku. +--- + +# Vagrant Push + +## Heroku Strategy + +[Heroku][] is a public PAAS provider that makes it easy to deploy an +application. The Vagrant Push Heroku strategy pushes your application's code to +Heroku. + +
+

+ Warning: The Vagrant Push Heroku strategy requires you + have configured your Heroku credentials and created the Heroku application. + This documentation will not cover these prerequisites, but you can read more + about them in the Heroku documentation. +

+
+ +Only files which are committed to the Git repository will be pushed to Heroku. +Additionally, the current working branch is always pushed to the Heroku, even if +it is not the "master" branch. + +The Vagrant Push Heroku strategy supports the following configuration options: + +- `app` - The name of the Heroku application. If the Heroku application does not + exist, an exception will be raised. If this value is not specified, the + basename of the directory containing the `Vagrantfile` is assumed to be the + name of the Heroku application. Since this value can change between users, it + is highly recommended that you add the `app` setting to your `Vagrantfile`. + +- `dir` - The base directory containing the Git repository to upload to Heroku. + By default this is the same directory as the Vagrantfile, but you can specify + this if you have a nested Git directory. + +- `remote` - The name of the Git remote where Heroku is configured. The default + value is "heroku". + + +### Usage + +The Vagrant Push Heroku strategy is defined in the `Vagrantfile` using the +`heroku` key: + +```ruby +config.push.define "heroku" do |push| + push.app = "my_application" +end +``` + +And then push the application to Heroku: + +```shell +$ vagrant push +``` + +[Heroku]: https://heroku.com/ "Heroku" diff --git a/website/docs/source/v2/push/index.html.md b/website/docs/source/v2/push/index.html.md new file mode 100644 index 000000000..62f696ea7 --- /dev/null +++ b/website/docs/source/v2/push/index.html.md @@ -0,0 +1,59 @@ +--- +page_title: "Vagrant Push" +sidebar_current: "push" +description: |- + Vagrant Push is a revolutionary +--- + +# Vagrant Push + +As of version 1.7, Vagrant is capable of deploying or "pushing" application code +in the same directory as your Vagrantfile to a remote such as an FTP server or +[HashiCorp's Atlas][Atlas]. + +Pushes are defined in an application's `Vagrantfile` and are invoked using the +`vagrant push` subcommand. Much like other components of Vagrant, each Vagrant +Push plugin has its own configuration options. Please consult the documentation +for your Vagrant Push plugin for more information. Here is an example Vagrant +Push configuration section in a `Vagrantfile`: + +```ruby +config.push.define "ftp" do |push| + push.host = "ftp.company.com" + push.username = "..." + # ... +end +``` + +When the application is ready to be deployed to the FTP server, just run a +single command: + +```shell +$ vagrant push +``` + +Much like [Vagrant Providers][], Vagrant Push also supports multiple backend +declarations. Consider the common scenario of a staging and QA environment: + +```ruby +config.push.define "staging", strategy: "ftp" do |push| + # ... +end + +config.push.define "qa", strategy: "ftp" do |push| + # ... +end +``` + +In this scenario, the user must pass the name of the Vagrant Push to the +subcommand: + +```shell +$ vagrant push staging +``` + +Vagrant Push is the easiest way to deploy your application. You can read more +in the documentation links on the sidebar. + +[Atlas]: https://atlas.hashicorp.com/ "HashiCorp's Atlas Service" +[Vagrant Providers]: /v2/providers/index.html "Vagrant Providers" diff --git a/website/docs/source/v2/push/local-exec.html.md b/website/docs/source/v2/push/local-exec.html.md new file mode 100644 index 000000000..11ba906c8 --- /dev/null +++ b/website/docs/source/v2/push/local-exec.html.md @@ -0,0 +1,72 @@ +--- +page_title: "Vagrant Push - Local Exec Strategy" +sidebar_current: "push-local-exec" +description: |- + The Vagrant Push Local Exec strategy pushes your application's code using a + user-defined script. +--- + +# Vagrant Push + +## Local Exec Strategy + +The Vagrant Push Local Exec strategy allows the user to invoke an arbitrary +shell command or script as part of a push. + +
+

+ Warning: The Vagrant Push Local Exec strategy does not + perform any validation on the correctness of the shell script. +

+
+ +The Vagrant Push Local Exec strategy supports the following configuration +options: + +- `script` - The path to a script on disk (relative to the `Vagrantfile`) to + execute. Vagrant will attempt to convert this script to an executable, but an + exception will be raised if that fails. +- `inline` - The inline script to execute (as a string). + +Please note - only one of the `script` and `inline` options may be specified in +a single push definition. + +### Usage + +The Vagrant Push Local Exec strategy is defined in the `Vagrantfile` using the +`local-exec` key: + +Remote path: + +```ruby +config.push.define "local-exec" do |push| + push.inline = <<-SCRIPT + scp -r . server:/var/www/website + SCRIPT +end +``` + +Local path: + +```ruby +config.push.define "local-exec" do |push| + push.inline = <<-SCRIPT + cp -r . /var/www/website + SCRIPT +end +``` + +For more complicated scripts, you may store them in a separate file and read +them from the `Vagrantfile` like so: + +```ruby +config.push.define "local-exec" do |push| + push.script = "my-script.sh" +end +``` + +And then invoke the push with Vagrant: + +```shell +$ vagrant push +``` diff --git a/website/docs/source/v2/share/connect.html.md b/website/docs/source/v2/share/connect.html.md index 37a3bf8e9..c7074d675 100644 --- a/website/docs/source/v2/share/connect.html.md +++ b/website/docs/source/v2/share/connect.html.md @@ -29,7 +29,7 @@ machines. `vagrant connect` creates a tiny virtual machine that takes up only around 20 MB in RAM, using VirtualBox or VMware (more provider support is coming soon). -Any traffic sent to this tiny virtual machine is then proxies through to +Any traffic sent to this tiny virtual machine is then proxied through to the shared Vagrant environment as if it were directed at it. ## Beware: Vagrant Insecure Key diff --git a/website/docs/source/v2/share/http.html.md b/website/docs/source/v2/share/http.html.md index 8c8a88830..0e99b5127 100644 --- a/website/docs/source/v2/share/http.html.md +++ b/website/docs/source/v2/share/http.html.md @@ -12,7 +12,7 @@ sharing," and is enabled by default when `vagrant share` is used. Because this mode of sharing creates a publicly accessible URL, the accessing party does not need to have Vagrant installed in order to view your environment. -This has a number of useful use cases: you can test webooks by exposing +This has a number of useful use cases: you can test webhooks by exposing your Vagrant environment to the internet, you can show your work to clients, teammates, or managers, etc. diff --git a/website/docs/source/v2/share/index.html.md b/website/docs/source/v2/share/index.html.md index 078fd5e59..20fde2781 100644 --- a/website/docs/source/v2/share/index.html.md +++ b/website/docs/source/v2/share/index.html.md @@ -34,4 +34,4 @@ to the left. We also have a section where we go into detail about the security implications of this feature. Vagrant Share requires an account with -[Vagrant Cloud](https://vagrantcloud.com) to be used. +[HashiCorp's Atlas](https://atlas.hashicorp.com) to be used. diff --git a/website/docs/source/v2/synced-folders/basic_usage.html.md b/website/docs/source/v2/synced-folders/basic_usage.html.md index 2e1be7e43..a6d67fd74 100644 --- a/website/docs/source/v2/synced-folders/basic_usage.html.md +++ b/website/docs/source/v2/synced-folders/basic_usage.html.md @@ -27,9 +27,10 @@ if it doesn't exist. ## Options -As an optional third parameter to configuring synced folders, you may specify -some options. These options are listed below. More detailed examples of using -some of these options are shown below this section. +You may also specify additional optional parameters when configuring +synced folders. These options are listed below. More detailed examples of using +some of these options are shown below this section, note the owner/group example +supplies two additional options separated by commas. In addition to these options, the specific synced folder type might allow more options. See the documentation for your specific synced folder @@ -66,7 +67,7 @@ Synced folders are automatically setup during `vagrant up` and ## Disabling -Shared folders can be disabled by adding the `disabled` option to +Synced folders can be disabled by adding the `disabled` option to any definition: ```ruby @@ -75,6 +76,12 @@ Vagrant.configure("2") do |config| end ``` +Disabling the default `/vagrant` share can be done as follows: + +```ruby +config.vm.synced_folder ".", "/vagrant", disabled: true +``` + ## Modifying the Owner/Group By default, Vagrant mounts the synced folders with the owner/group set @@ -85,3 +92,15 @@ owner and group. It is possible to set these options: config.vm.synced_folder "src/", "/srv/website", owner: "root", group: "root" ``` + +## Symbolic Links + +Support for symbolic links across synced folder implementations and +host/guest combinations is not consistent. Vagrant does its best to +make sure symbolic links work by configuring various hypervisors (such +as VirtualBox), but some host/guest combinations still do not work +properly. This can affect some development environments that rely on +symbolic links. + +The recommendation is to make sure to test symbolic links on all the +host/guest combinations you sync folders on if this is important to you. diff --git a/website/docs/source/v2/synced-folders/nfs.html.md b/website/docs/source/v2/synced-folders/nfs.html.md index 1e0959581..8aedf6188 100644 --- a/website/docs/source/v2/synced-folders/nfs.html.md +++ b/website/docs/source/v2/synced-folders/nfs.html.md @@ -65,6 +65,63 @@ the final part of the `config.vm.synced_folder` definition, along with the * `nfs_version` (string | integer) - The NFS protocol version to use when mounting the folder on the guest. This defaults to 3. +## NFS Global Options + +There are also more global NFS options you can set with `config.nfs` in +the Vagrantfile. These are documented below: + +* `functional` (bool) - Defaults to true. If false, then NFS won't be used + as a synced folder type. If a synced folder specifically requests NFS, + it will error. + +* `map_uid` and `map_gid` (int) - The UID/GID, respectively, to map all + read/write requests too. This will not affect the owner/group within the + guest machine itself, but any writes will behave as if they were written + as this UID/GID on the host. This defaults to the current user running + Vagrant. + +* `verify_installed` (bool) - Defaults to true. If this is false, then + Vagrant will skip checking if NFS is installed. + +## Specifying NFS Arguments + +In addition to the options specified above, it is possible for Vagrant to +specify alternate NFS arguments when mounting the NFS share by using the +`mount_options` key. For example, to use the `actimeo=2` client mount option: + +``` +config.vm.synced_folder ".", "/vagrant", + :nfs => true, + :mount_options => ['actimeo=2'] +``` + +This would result in the following `mount` command being executed on the guest: + +``` +mount -o 'actimeo=2' 172.28.128.1:'/path/to/vagrantfile' /vagrant +``` + +You can also tweak the arguments specified in the `/etc/exports` template +when the mount is added, by using the OS-specific `linux__nfs_options` or +`bsd__nfs_options` keys. Note that these options completely override the default +arguments that are added by Vagrant automatically. For example, to make the +NFS share asynchronous: + +``` +config.vm.synced_folder ".", "/vagrant", + :nfs => true, + :linux__nfs_options => ['rw','no_subtree_check','all_squash','async'] +``` + +This would result in the following content in `/etc/exports` on the host (note +the added `async` flag): + +``` +# VAGRANT-BEGIN: 21171 5b8f0135-9e73-4166-9bfd-ac43d5f14261 +"/path/to/vagrantfile" 172.28.128.5(rw,no_subtree_check,all_squash,async,anonuid=21171,anongid=660,fsid=3382034405) +# VAGRANT-END: 21171 5b8f0135-9e73-4166-9bfd-ac43d5f14261 +``` + ## Root Privilege Requirement To configure NFS, Vagrant must modify system files on the host. Therefore, @@ -90,7 +147,7 @@ Cmnd_Alias VAGRANT_EXPORTS_REMOVE = /usr/bin/sed -E -e /*/ d -ibak /etc/exports %admin ALL=(root) NOPASSWD: VAGRANT_EXPORTS_ADD, VAGRANT_NFSD, VAGRANT_EXPORTS_REMOVE ``` -For Linux, sudoers should look like this: +For Ubuntu Linux , sudoers should look like this: ``` Cmnd_Alias VAGRANT_EXPORTS_ADD = /usr/bin/tee -a /etc/exports @@ -101,3 +158,22 @@ Cmnd_Alias VAGRANT_EXPORTS_REMOVE = /bin/sed -r -e * d -ibak /etc/exports %sudo ALL=(root) NOPASSWD: VAGRANT_EXPORTS_ADD, VAGRANT_NFSD_CHECK, VAGRANT_NFSD_START, VAGRANT_NFSD_APPLY, VAGRANT_EXPORTS_REMOVE ``` +For Fedora Linux, sudoers might look like this (given your user +belongs to the vagrant group): + +``` +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 +``` + +## Other Notes + +**Encrypted folders:** If you have an encrypted disk, then NFS very often +will refuse to export the filesystem. The error message given by NFS is +often not clear. One error message seen is ` does not support NFS`. +There is no workaround for this other than sharing a directory which isn't +encrypted. diff --git a/website/docs/source/v2/synced-folders/rsync.html.md b/website/docs/source/v2/synced-folders/rsync.html.md index 7393baa9a..656731c96 100644 --- a/website/docs/source/v2/synced-folders/rsync.html.md +++ b/website/docs/source/v2/synced-folders/rsync.html.md @@ -35,12 +35,16 @@ can automatically install rsync into many operating systems. If Vagrant is unable to automatically install rsync for your operating system, it will tell you. +The destination folder will be created as the user initiating the connection, +this is `vagrant` by default. This user requires the appropiate permissions on +the destination folder. + ## Options The rsync synced folder type accepts the following options: * `rsync__args` (array of strings) - A list of arguments to supply - to `rsync`. By default this is `["--verbose", "--archive", "--delete", "-z"]`. + to `rsync`. By default this is `["--verbose", "--archive", "--delete", "-z", "--copy-links"]`. * `rsync__auto` (boolean) - If false, then `rsync-auto` will not watch and automatically sync this folder. By default, this is true. @@ -58,6 +62,14 @@ The rsync synced folder type accepts the following options: pattern. By default, the ".vagrant/" directory is excluded. We recommend excluding revision control directories such as ".git/" as well. +* `rsync__rsync_path` (string) - The path on the remote host where rsync + is and how it is executed. This is platform specific but defaults to + "sudo rsync" for many guests. + +* `rsync__verbose` (boolean) - If true, then the output from the rsync + process will be echoed to the console. The output of rsync is subject + to `rsync__args` of course. By default, this is false. + ## Example The following is an example of using RSync to sync a folder: @@ -68,3 +80,16 @@ Vagrant.configure("2") do |config| rsync__exclude: ".git/" end + +## Rsync to a restricted folder + +If required to copy to a destination where `vagrant` user doesn't have +permissions, use `"--rsync-path='sudo rsync'"` to run rsync with sudo on the guest + +
+Vagrant.configure("2") do |config|
+  config.vm.synced_folder "bin", "/usr/local/bin", type: "rsync",
+    rsync__exclude: ".git/",
+    rsync__args: ["--verbose", "--rsync-path='sudo rsync'", "--archive", "--delete", "-z"]
+end
+
diff --git a/website/docs/source/v2/synced-folders/smb.html.md b/website/docs/source/v2/synced-folders/smb.html.md index 130a7dabf..65d679794 100644 --- a/website/docs/source/v2/synced-folders/smb.html.md +++ b/website/docs/source/v2/synced-folders/smb.html.md @@ -25,7 +25,7 @@ alternative to some other mechanisms such as VirtualBox shared folders. ## Prerequisites To use the SMB synced folder type, the machine running Vagrant must be -a Windows machine. In addition to this, the command prompt executing Vagrant +a Windows machine with PowerShell version 3 or later installed. In addition to this, the command prompt executing Vagrant must have administrative privileges. Vagrant requires these privileges in order to create new network folder shares. @@ -49,8 +49,8 @@ The SMB synced folder type has a variety of options it accepts: * `smb_username` (string) - The username used for authentication to mount the SMB mount. This is the username to access the mount, _not_ the username of the account where the folder is being mounted to. This is usually your - Windows username. If this isn't specified, Vagrant will prompt you for - it. + Windows username. If you sign into a domain, specify it as `user@domain`. + If this option isn't specified, Vagrant will prompt you for it. ## Example @@ -62,6 +62,17 @@ Vagrant.configure("2") do |config| end +## Preventing Idle Disconnects + +On Windows, if a file isn't accessed for some period of time, it may +disconnect from the guest and prevent the guest from accessing the SMB-mounted +share. To prevent this, the following command can be used in a superuser +shell. Note that you should research if this is the right option for you. + +
+net config server /autodisconnect:-1
+
+ ## Limitations Because SMB is a relatively new synced folder type in Vagrant, it still diff --git a/website/docs/source/v2/vagrantfile/index.html.md b/website/docs/source/v2/vagrantfile/index.html.md index 66de9b7a1..a83b98620 100644 --- a/website/docs/source/v2/vagrantfile/index.html.md +++ b/website/docs/source/v2/vagrantfile/index.html.md @@ -9,7 +9,7 @@ The primary function of the Vagrantfile is to describe the type of machine required for a project, and how to configure and provision these machines. Vagrantfiles are called Vagrantfiles because the actual literal filename for the file is `Vagrantfile` (casing doesn't -matter). +matter unless your file system is running in a strict case sensitive mode). Vagrant is meant to run with one Vagrantfile per project, and the Vagrantfile is supposed to be committed to version control. This allows other developers diff --git a/website/docs/source/v2/vagrantfile/machine_settings.html.md b/website/docs/source/v2/vagrantfile/machine_settings.html.md index cfcca719b..c517146bb 100644 --- a/website/docs/source/v2/vagrantfile/machine_settings.html.md +++ b/website/docs/source/v2/vagrantfile/machine_settings.html.md @@ -20,7 +20,7 @@ for the machine to boot and be accessible. By default this is 300 seconds. `config.vm.box` - This configures what [box](/v2/boxes.html) the machine will be brought up against. The value here should be the name of an installed box or a shorthand name of a box in -[Vagrant Cloud](https://vagrantcloud.com). +[HashiCorp's Atlas](https://atlas.hashicorp.com).
@@ -28,7 +28,7 @@ of an installed box or a shorthand name of a box in the configured box on every `vagrant up`. If an update is found, Vagrant will tell the user. By default this is true. Updates will only be checked for boxes that properly support updates (boxes from -[Vagrant Cloud](https://vagrantcloud.com) +[HashiCorp's Atlas](https://atlas.hashicorp.com) or some other versioned box).
@@ -56,14 +56,32 @@ certificate is used to download the box.
+`config.vm.box_download_ca_cert` - Path to a CA cert bundle to use when +downloading a box directly. By default, Vagrant will use the Mozilla CA cert +bundle. + +
+ +`config.vm.box_download_ca_path` - Path to a directory containing +CA certificates for downloading a box directly. By default, Vagrant will +use the Mozilla CA cert bundle. + +
`config.vm.box_download_insecure` - If true, then SSL certificates from the server will not be verified. By default, if the URL is an HTTPS URL, then SSL certs will be verified.
+`config.vm.box_download_location_trusted` - If true, then all HTTP redirects will be +treated as trusted. That means credentials used for initial URL will be used for +all subsequent redirects. By default, redirect locations are untrusted so credentials +(if specified) used only for initial HTTP request. + +
+ `config.vm.box_url` - The URL that the configured box can be found at. -If `config.vm.box` is a shorthand to a box in [Vagrant Cloud](https://vagrantcloud.com) +If `config.vm.box` is a shorthand to a box in [HashiCorp's Atlas](https://atlas.hashicorp.com) then this value doesn't need to be specified. Otherwise, it should point to the proper place where the box can be found if it isn't installed. @@ -85,6 +103,12 @@ constraints.
+`config.vm.communicator` - The communicator type to use to connect to the +guest box. By default this is `"ssh"`, but should be changed to `"winrm"` for +Windows guests. + +
+ `config.vm.graceful_halt_timeout` - The time in seconds that Vagrant will wait for the machine to gracefully halt when `vagrant halt` is called. Defaults to 60 seconds. diff --git a/website/docs/source/v2/vagrantfile/ssh_settings.html.md b/website/docs/source/v2/vagrantfile/ssh_settings.html.md index ed0d2bde8..b07835b75 100644 --- a/website/docs/source/v2/vagrantfile/ssh_settings.html.md +++ b/website/docs/source/v2/vagrantfile/ssh_settings.html.md @@ -22,7 +22,7 @@ public boxes are made as. `config.ssh.password` - This sets a password that Vagrant will use to authenticate the SSH user. Note that Vagrant recommends you use key-based -authentiation rather than a password (see `private_key_path`) below. If +authentication rather than a password (see `private_key_path`) below. If you use a password, Vagrant will automatically insert a keypair if `insert_key` is true. @@ -68,8 +68,13 @@ is enabled. Defaults to false.
`config.ssh.insert_key` - If `true`, Vagrant will automatically insert -an insecure keypair to use for SSH. By default, this is true. This only -has an effect if you don't already use private keys for authentication. +an keypair to use for SSH, replacing the default Vagrant's insecure key +inside the machine if detected. By default, this is true. + +This only has an effect if you don't already use private keys for +authentication or if you are relying on the default insecure key. +If you don't have to take care about security in your project and want to +keep using the default insecure key, set this to `false`.
@@ -93,3 +98,9 @@ a way to not use a pty, that is recommended instead. Vagrant. By default this is `bash -l`. Note that this has no effect on the shell you get when you run `vagrant ssh`. This configuration option only affects the shell to use when executing commands internally in Vagrant. + +
+ +`config.ssh.sudo_command` - The command to use when executing a command +with `sudo`. This defaults to `sudo -E -H %c`. The `%c` will be replaced by +the command that is being executed. diff --git a/website/docs/source/v2/vagrantfile/version.html.md b/website/docs/source/v2/vagrantfile/version.html.md index fc2503c51..326128849 100644 --- a/website/docs/source/v2/vagrantfile/version.html.md +++ b/website/docs/source/v2/vagrantfile/version.html.md @@ -14,9 +14,7 @@ If you run `vagrant init` today, the Vagrantfile will be in roughly the following format: ```ruby -VAGRANTFILE_API_VERSION = "2" - -Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| +Vagrant.configure(2) do |config| # ... end ``` diff --git a/website/docs/source/v2/vagrantfile/winrm_settings.html.md b/website/docs/source/v2/vagrantfile/winrm_settings.html.md new file mode 100644 index 000000000..add316a40 --- /dev/null +++ b/website/docs/source/v2/vagrantfile/winrm_settings.html.md @@ -0,0 +1,60 @@ +--- +page_title: "config.winrm - Vagrantfile" +sidebar_current: "vagrantfile-winrm" +--- + +# WinRM Settings + +**Config namespace: `config.winrm`** + +The settings within `config.winrm` relate to configuring how Vagrant +will access your Windows guest over WinRM. As with most Vagrant settings, the +defaults are typically fine, but you can fine tune whatever you'd like. + +These settings are only used if you've set your communicator type to `:winrm`. + +## Available Settings + +`config.winrm.username` - This sets the username that Vagrant will use +to login to the WinRM web service by default. Providers are free to override +this if they detect a more appropriate user. By default this is "vagrant," +since that is what most public boxes are made as. + +
+ +`config.winrm.password` - This sets a password that Vagrant will use to +authenticate the WinRM user. By default this is "vagrant," since that is +what most public boxes are made as. + +
+ +`config.winrm.host` - The hostname or IP to connect to the WinRM service. +By default this is empty, because the provider usually figures this out for +you. + +
+ +`config.winrm.port` - The WinRM port to connect to, by default 5985. + +
+ +`config.winrm.guest_port` - The port on the guest that WinRM is running on. +This is used by some providers to detect forwarded ports for WinRM. For +example, if this is set to 5985 (the default), and Vagrant detects a forwarded +port to port 5985 on the guest from port 4567 on the host, Vagrant will attempt +to use port 4567 to talk to the guest if there is no other option. + +
+ +Warning: In order for Vagrant to communicate with a Windows +guest, you must allow unencrypted WinRM connections on the guest machine +itself. Some public boxes already have this configured, but if you are +attempting to `vagrant up` a Windows box and the command hangs at +`Waiting for WinRM to become available...`, then you will need to run the +commands below on the guest machine itself, at the box setup stage, +after provisioning, or through a start up script. + +
+Set-Item WSMan:\localhost\Service\AllowUnencrypted -Value True
+Set-Item WSMan:\localhost\Service\Auth\Basic -Value True
+
\ No newline at end of file diff --git a/website/docs/source/v2/virtualbox/common-issues.html.md b/website/docs/source/v2/virtualbox/common-issues.html.md index bdb61ae8b..cea2f6df6 100644 --- a/website/docs/source/v2/virtualbox/common-issues.html.md +++ b/website/docs/source/v2/virtualbox/common-issues.html.md @@ -20,3 +20,10 @@ it with the same access level as the console running Vagrant. To fix this issue, completely shut down all VirtualBox machines and GUIs. Wait a few seconds. Then, launch VirtualBox only with the access level you wish to use. + +## DNS Not Working + +If DNS is not working within your VM, then you may need to enable +a DNS proxy (built-in to VirtualBox). Please [see the StackOverflow answers +here](http://serverfault.com/questions/453185/vagrant-virtualbox-dns-10-0-2-3-not-working) +for a guide on how to do that. diff --git a/website/docs/source/v2/virtualbox/index.html.md b/website/docs/source/v2/virtualbox/index.html.md index 891502e85..ba099eb4f 100644 --- a/website/docs/source/v2/virtualbox/index.html.md +++ b/website/docs/source/v2/virtualbox/index.html.md @@ -9,8 +9,8 @@ Vagrant comes with support out of the box for [VirtualBox](http://www.virtualbox a free, cross-platform consumer virtualization product. The VirtualBox provider is compatible with VirtualBox versions 4.0.x, 4.1.x, -4.2.x, and 4.3.x. Any other version is unsupported and the provider will display -an error message. +4.2.x, 4.3.x, and 5.0.x. Any other version is unsupported and the provider will +display an error message. VirtualBox must be installed on its own prior to using the provider, or the provider will display an error message asking you to install it. diff --git a/website/docs/source/v2/virtualbox/networking.html.md b/website/docs/source/v2/virtualbox/networking.html.md index 037c10140..55e7d4c4c 100644 --- a/website/docs/source/v2/virtualbox/networking.html.md +++ b/website/docs/source/v2/virtualbox/networking.html.md @@ -33,3 +33,21 @@ Vagrant.configure("2") do |config| virtualbox__intnet: "mynetwork" end ``` + +## VirtualBox NIC Type + +You can specify a specific nictype for the created network interface +by using the `nictype` parameter. This isn't prefixed by `virtualbox__` +for legacy reasons, but is VirtualBox-specific. + +This is an advanced option and should only be used if you know what +you're using, since it can cause the network device to not work at all. + +Example: + +```ruby +Vagrant.configure("2") do |config| + config.vm.network "private_network", ip: "192.168.50.4", + nictype: "virtio" +end +``` diff --git a/website/docs/source/v2/virtualbox/usage.html.md b/website/docs/source/v2/virtualbox/usage.html.md index 85c0f047f..282cbc6d3 100644 --- a/website/docs/source/v2/virtualbox/usage.html.md +++ b/website/docs/source/v2/virtualbox/usage.html.md @@ -5,8 +5,11 @@ sidebar_current: "virtualbox-usage" # Usage -The VirtualBox provider is used just like any other provider. Please +The Vagrant VirtualBox provider is used just like any other provider. Please read the general [basic usage](/v2/providers/basic_usage.html) page for providers. The value to use for the `--provider` flag is `virtualbox`. + +The Vagrant VirtualBox provider does not support parallel execution at this +time. Specifying the `--parallel` option will have no effect. diff --git a/website/docs/source/v2/vmware/index.html.md b/website/docs/source/v2/vmware/index.html.md index cf0bebc55..1189254e0 100644 --- a/website/docs/source/v2/vmware/index.html.md +++ b/website/docs/source/v2/vmware/index.html.md @@ -21,10 +21,11 @@ VirtualBox feature that Vagrant supports is fully functional in VMware as well. However, there are some VMware-specific things such as box formats, configurations, etc. that are documented here. -The VMware provider is currently compatible with Fusion 5 and 6 or Workstation 9 and 10. -Fusion and Workstation must be purchased and installed separately prior to using the -provider. If the proper VMware software isn't properly installed, or an invalid version is -installed, the provider will give you a human-friendly error message. +For the most up-to-date information on compatability and supported versions of +VMware Fusion and VMware Workstation, please visit the +[Vagrant VMware product page](https://www.vagrantup.com/vmware). Please note +that VMware Fusion and VMware Workstation are third-party products that must be +purchased and installed separately prior to using the provider. Use the navigation to the left to find a specific VMware topic to read more about. diff --git a/website/docs/source/v2/vmware/installation.html.md b/website/docs/source/v2/vmware/installation.html.md index c9a7ca561..2e6ee1d15 100644 --- a/website/docs/source/v2/vmware/installation.html.md +++ b/website/docs/source/v2/vmware/installation.html.md @@ -5,36 +5,123 @@ sidebar_current: "vmware-installation" # Installation -The VMware provider is installed using -[standard plugin installation procedures](/v2/plugins/usage.html). -In addition to installing the plugin, a license must be provided so that -it can be loaded properly. +The Vagrant VMware provider can be installed using the standard plugin +installation procedure. VMware Fusion users should run: -To purchase a license, visit the [Vagrant VMware provider](http://www.vagrantup.com/vmware) -page. After purchasing a license, a link to download it will be emailed -to you. Download this file. - -Installing the plugin and license is easy using the `vagrant plugin` -interface. Both steps are shown below. Note that the plugin name is different -depending on the provider you're using. If you're using the Fusion provider, -it is `vagrant-vmware-fusion`. If you're using the Workstation provider, -it is `vagrant-vmware-workstation`. - -``` +```text $ vagrant plugin install vagrant-vmware-fusion -... -$ vagrant plugin license vagrant-vmware-fusion license.lic -... ``` -For licensing, the first parameter is the name of the plugin, which is -`vagrant-vmware-fusion` for the VMware Fusion provider, or `vagrant-vmware-workstation` -for the VMware Workstation provider. The second parameter is the path to the license itself. +VMware Workstation users should run: -You can verify the license is properly installed by running any -Vagrant command, such as `vagrant plugin list`. The VMware -provider will show an error message if you are missing a license or using an -invalid license. +```text +$ vagrant plugin install vagrant-vmware-workstation +``` -If there are problems installing the plugin, please contact -[support](http://www.vagrantup.com/support.html). +For more information on plugin installation, please see the +[Vagrant plugin usage documentation](/v2/plugins/usage.html). + +The Vagrant VMware plugins are commercial products provided by +[HashiCorp](https://www.hashicorp.com) and **require the purchase of a license** +to operate. To purchase a license, please visit the +[Vagrant VMware provider](http://www.vagrantup.com/vmware#buy-now) page. Upon +purchasing a license, you will receive a license file in your inbox. Download +this file and save it to a temporary location on your computer. + +
+ Warning! You cannot use your VMware product license as a + Vagrant VMware plugin license. They are separate commercial products, each + requiring their own license. +
+ +After installing the correct Vagrant VMware product plugin for your system, you +will need to install the license. For VMware Fusion users: + +```text +$ vagrant plugin license vagrant-vmware-fusion ~/license.lic +``` + +For VMware Workstation users: + +```text +$ vagrant plugin license vagrant-vmware-workstation ~/license.lic +``` + +The first parameter is the name of the plugin, and the second parameter is the +path to the license file on disk. Please be sure to replace `~/license.lic` +with the path where you temporarily saved the downloaded license file to disk. +After you have installed the plugin license, you may remove the temporary file. + +To verify the license installation, run: + +```text +$ vagrant plugin list +``` + +If the license is not installed correctly, you will see an error message. + + +## Frequently Asked Questions + +**Q: I purchased a Vagrant VMware plugin license, but I did not receive an email?**
+First, please check your JUNK or SPAM folders. Since the license comes from an +automated system, it might have been flagged as spam by your email provider. If +you do not see the email there, please [contact support](mailto:support@hashicorp.com?subject=License Not Received) +and include the original order number. + +**Q: Do I need to keep the Vagrant VMware plugin license file on disk?**
+After you have installed the Vagrant VMware plugin license, it is safe to remove +your copy from disk. Vagrant copies the license into its structure for reference +on boot. + +**Q: I lost my original email, where can I download my Vagrant VMware plugin license again?**
+Please [contact support](mailto:support@hashicorp.com?subject=Lost My License&body=Hello support! I seem to have misplaced my Vagrant VMware license. Could you please send it to me? Thanks!). **Note:** +please contact support using the email address with which you made the +original purchase. If you use an alternate email, you will be asked to verify +that you are the owner of the requested license. + +**Q: I upgraded my VMware product and now my license is invalid?**
+The Vagrant VMware plugin licenses are valid for specific VMware product +versions at the time of purchase. When new versions of VMware products are +released, significant changes to the plugin code are often required to support +this new version. For this reason, you may need to upgrade your current license +to work with the new version of the VMware product. Customers can check their +license upgrade eligibility by visiting the [License Upgrade Center](http://license.hashicorp.com/upgrade/vmware2014) +and entering the email address with which they made the original purchase. + +Your existing license will continue to work with all previous versions of the +VMware products. If you do not wish to update at this time, you can rollback +your VMware installation to an older version. + +**Q: Why is the Vagrant VMware plugin not working with my trial version of VMware Fusion/Workstation?**
+The Vagrant VMware Fusion and Vagrant VMware Workstation plugins are not +compatible with trial versions of the VMware products. We apologize for the +inconvenience. + +**Q: I accidentally bought the wrong Vagrant VMware plugin, can I switch?**
+Sure! Even though the Vagrant VMware Fusion plugin and the Vagrant VMware +Workstation plugin are different products, they are the same price and fall +under the same EULA. As such, we can transfer the license for you. Please +[contact support](mailto:support@hashicorp.com?subject=Transfer License). + +**Q: How do I upgrade my currently installed Vagrant VMware plugin?**
+You can update the Vagrant VMware plugin to the latest version by re-running the +install command. For VMware Fusion: + +```text +$ vagrant plugin install vagrant-vmware-fusion +``` + +For VMWare Workstation: + +```text +$ vagrant plugin install vagrant-vmware-workstation +``` + + +## Support +If you have any issues purchasing, installing, or using the Vagrant VMware +plugins, please [contact support](http://www.vagrantup.com/support.html). To +expedite the support process, please include the +[Vagrant debug output](/v2/other/debugging.html) as a Gist or pastebin if +applicable. This will help us more quickly diagnose your issue. diff --git a/website/docs/source/v2/vmware/kernel-upgrade.html.md b/website/docs/source/v2/vmware/kernel-upgrade.html.md new file mode 100644 index 000000000..b91986730 --- /dev/null +++ b/website/docs/source/v2/vmware/kernel-upgrade.html.md @@ -0,0 +1,55 @@ +--- +page_title: "Kernel Upgrade - VMware Provider" +sidebar_current: "vmware-kernel-upgrade" +--- + +# Kernel Upgrade + +If as part of running your Vagrant environment with VMware, you perform +a kernel upgrade, it is likely that the VMware guest tools will stop working. +This breaks features of Vagrant such as synced folders and sometimes +networking as well. + +This page documents how to upgrade your kernel and keep your guest tools +functioning. If you're not planning to upgrade your kernel, then you can safely +skip this page. + +## Enable Auto-Upgrade of VMware Tools + +If you're running a common OS, VMware tools can often auto-upgrade themselves. +This setting is disabled by default. The Vagrantfile settings below will +enable auto-upgrading: + +```ruby +# Ensure that VMWare Tools recompiles kernel modules +# when we update the linux images +$fix_vmware_tools_script = < @@ -91,7 +79,7 @@
@@ -105,6 +93,40 @@ + + + + + + diff --git a/website/www/source/support.html.erb b/website/www/source/support.html.erb index 56ffe947c..f1afa2385 100644 --- a/website/www/source/support.html.erb +++ b/website/www/source/support.html.erb @@ -58,7 +58,7 @@ here, but your inquiries will be seen by many more individuals.

Professional Support

Professional support packages are on the way. If you're interested -in hearing more, please contact biz@hashicorp.com. +in hearing more, please contact sales@hashicorp.com.

If you purchased the VMware Fusion provider, diff --git a/website/www/source/vmware/index.html.erb b/website/www/source/vmware/index.html.erb index 3ee061ab8..7323deb5f 100644 --- a/website/www/source/vmware/index.html.erb +++ b/website/www/source/vmware/index.html.erb @@ -174,7 +174,13 @@ page_title: "VMware Vagrant Environments"

The provider license does not include a license to the VMware - software, which must be purchased separately. If you're buying over 100 licenses, contact biz@hashicorp.com for volume pricing. + software, which must be purchased separately. If you're buying over 150 licenses, contact sales@hashicorp.com for volume pricing. +
+
+ Previous plugin versions may not support the latest VMware products. Please visit the license upgrade center to check if your license requires an upgrade before you upgrade your VMware products. +
+
+ For reseller information, click here.
@@ -182,4 +188,203 @@ page_title: "VMware Vagrant Environments" + +
+
+
+
+

+ Frequently Asked Questions +

+
+
+ +
+
+

+ Do you offer a trial for the Vagrant VMware plugins? +

+

+ We do not offer a trial mechanism at this time, but we do offer a + 30-day, no questions asked, 100% money-back guarantee. If you are + not satisfied with the product, contact us within 30 days and you + will receive a full refund. +

+
+
+ +
+
+

+ Do you offer educational discounts on the Vagrant VMware plugins? +

+

+ We offer an academic discount of 10% for the Vagrant VMware plugins. + However, we require proof that you are a current student or employee + in academia. Please contact support with any one of + the following forms of proof: + +

    +
  • A picture of your current university ID

  • +
  • An email from your official .edu school email address

  • +
  • A copy of something on university letterhead indicating you are currently enrolled as a student

  • +
+

+
+
+ +
+
+

+ I already own a license, am I eligible for an upgrade? +

+

+ Existing license holders may check their upgrade eligibility by + visiting the license upgrade center. + If you are eligible for an upgrade, the system will generate a + unique discount code that may be used when purchasing the new + license. +

+
+
+ +
+
+

+ Do I need to pay for upgrades to my license? +

+

+ The Vagrant VMware plugin licenses are valid for specific VMware + product versions at the time of purchase. When new versions of + VMware products are released, significant changes to the plugin code + are often required to support this new version. For this reason, you + may need to upgrade your current license to work with the new + version of the VMware product. Customers can check their license + upgrade eligibility by visiting the + License Upgrade Center + and entering the email address with which they made the original + purchase. +

+

+ Please note: your existing license will continue to work with all + previous versions of the VMware products. If you do not wish to + update at this time, you can rollback your VMware installation to an + older version. +

+
+
+ +
+
+

+ Where can I find the EULA for the Vagrant VMware Plugins? +

+

+ The EULA for the Vagrant VMware plugins + is available on the Vagrant website. +

+
+
+ +
+
+

+ Do you offer incentives for resellers? +

+

+ All our reseller information can be found on the + Reseller Information page. +

+
+
+ +
+
+

+ Do you offer bulk/volume discounts for the Vagrant VMware plugins? +

+

+ We certainly do! Email support + with the number of licenses you need and we can give you bulk pricing + information. Please note that bulk pricing requires the purchase of + at least 150 seats. +

+
+
+ +
+
+

+ Since each license comes with two "seats", can I use one seat for the Vagrant VMware Fusion plugin and one seat for the Vagrant VMware Workstation plugin? +

+

+ Unfortunately this is not possible at this time. The Vagrant VMware + Fusion plugin and the Vagrant VMware Workstation plugin are separate + products and cannot be shared in this manner. We apologize for the + inconvenience. +

+

+ We do offer a 50% discount off the purchase of the other Vagrant + plugin for individual users and qualified candidates. Certain + restrictions do apply. Please email support + to find out if you qualify. Unfortuantely we cannot retroactively + apply this discount, so please email support before making your + purchase. +

+
+
+ +
+
+

+ I accidentally bought the wrong Vagrant VMware plugin, can I switch? +

+

+ Sure! Even though the Vagrant VMware Fusion plugin and the Vagrant + VMware Workstation plugin are different products, they are the same + price and fall under the same EULA. As such, we can transfer the + license for you. Please contact support + for assistance. Be sure to include your original order number. You + may be asked to confirm details your purchase to verify the + authenticity of your request. +

+
+
+ +
+
+

+ Does this include the VMware software? +

+

+ The Vagrant VMware Plugin requires the separate purchase of VMware + Fusion/Workstation from VMware. The VMware product is not bundled + with the plugin. +

+
+
+ +
+
+

+ Why is the Vagrant VMware plugin not working with my trial version of VMware Fusion/Workstation? +

+

+ While we have not been able to isolate to a specific issue or cause, + the Vagrant VMware Fusion and Vagrant VMware Workstation plugins are + sometimes incompatible with the trial versions of the VMware + products. +

+

+ Please try restarting your computer and running the VMware software + manually. Occassionally you must accept the license agreement before + VMware will run. If you do not see any errors when opening the + VMware GUI, you may need to purchase the full version to use the + plugin. We apologize for the inconvience. +

+
+
+
+
+ diff --git a/website/www/source/vmware/privacy-policy.html.md b/website/www/source/vmware/privacy-policy.html.md index d8c04ac88..4d6332e87 100644 --- a/website/www/source/vmware/privacy-policy.html.md +++ b/website/www/source/vmware/privacy-policy.html.md @@ -10,7 +10,7 @@ I. WHAT DOES THIS PRIVACY POLICY COVER? This Privacy Policy covers our treatment of personally identifiable information (“Personal Information”) that we gather when you are accessing or using our Services. This policy does not apply to the practices of companies that we do not own or control, or to individuals that we do not employ or manage. -We do not knowingly collect or solicit personal information from anyone under the age of 13 or knowingly allow such persons to register for the Services (as that term is defined in our Terms of Use). If you are under 13, please do not attempt to register for the Services or send any information about yourself to us, including your name, address, telephone number, or email address. No one under age 13 may provide any personal information to us or on the Services. In the event that we learn that we have collected personal information from a child under age 13 without verification of parental consent, we will delete that information as quickly as possible. If you believe that we might have any information from or about a child under 13, please contact us at biz@hashicorp.com. +We do not knowingly collect or solicit personal information from anyone under the age of 13 or knowingly allow such persons to register for the Services (as that term is defined in our Terms of Use). If you are under 13, please do not attempt to register for the Services or send any information about yourself to us, including your name, address, telephone number, or email address. No one under age 13 may provide any personal information to us or on the Services. In the event that we learn that we have collected personal information from a child under age 13 without verification of parental consent, we will delete that information as quickly as possible. If you believe that we might have any information from or about a child under 13, please contact us at support@hashicorp.com. We gather various types of Personal Information from our users, as explained more fully below. We may use this Personal Information to personalize and improve our services, to allow our users to set up a user account and profile, to contact users, to fulfill your requests for certain products and services, to analyze how users utilize the Services, and as otherwise set forth in this Privacy Policy. We may share certain types of Personal Information with third parties, as described below. @@ -20,7 +20,7 @@ A. Information You Provide to Us: We receive and store any information you knowingly provide to us. For example, we collect Personal Information such as your name, email address, and browser information,. You can choose not to provide us with certain information, but then you may not be able to register with us or to take advantage of some of our features. We may anonymize your Personal Information so that you cannot be individually identified, and provide that information to our partners. -If you have provided us with a means of contacting you, we may use such means to communicate with you. For example, we may send you promotional offers on behalf of other businesses, or communicate with you about your use of the Services. Also, we may receive a confirmation when you open a message from us. This confirmation helps us make our communications with you more interesting and improve our services. If you do not want to receive communications from us, please indicate your preference by emailing us at biz@hashicorp.com. Please note that if you do not want to receive legal notices from us, those legal notices will still govern your use of the Services, and you are responsible for reviewing such legal notices for changes. +If you have provided us with a means of contacting you, we may use such means to communicate with you. For example, we may send you promotional offers on behalf of other businesses, or communicate with you about your use of the Services. Also, we may receive a confirmation when you open a message from us. This confirmation helps us make our communications with you more interesting and improve our services. If you do not want to receive communications from us, please indicate your preference by emailing us at support@hashicorp.com. Please note that if you do not want to receive legal notices from us, those legal notices will still govern your use of the Services, and you are responsible for reviewing such legal notices for changes. B. Information Collected Automatically: @@ -53,19 +53,19 @@ The Services may contain links to other sites. We are not responsible for the pr V. WHAT PERSONAL INFORMATION CAN I ACCESS? -You may access, edit, or delete account information by emailing us at biz@hashicorp.com. +You may access, edit, or delete account information by emailing us at support@hashicorp.com. -The information you can view, update, and delete may change as the Services change. If you have any questions about viewing or updating information we have on file about you, please contact us at biz@hashicorp.com. Under California Civil Code Sections 1798.83-1798.84, California residents are entitled to ask us for a notice identifying the categories of Personal Information which we share with our affiliates and/or third parties for marketing purposes, and providing contact information for such affiliates and/or third parties. If you are a California resident and would like a copy of this notice, please submit a written request to: biz@hashicorp.com. +The information you can view, update, and delete may change as the Services change. If you have any questions about viewing or updating information we have on file about you, please contact us at support@hashicorp.com. Under California Civil Code Sections 1798.83-1798.84, California residents are entitled to ask us for a notice identifying the categories of Personal Information which we share with our affiliates and/or third parties for marketing purposes, and providing contact information for such affiliates and/or third parties. If you are a California resident and would like a copy of this notice, please submit a written request to: support@hashicorp.com. VI. WHAT CHOICES DO I HAVE? • You can always opt not to disclose information to use, but keep in mind some information may be needed to register with us or to take advantage of some of our special features. -• You may be able to add, update, or delete information as explained in Section V above. When you update information, however, we may maintain a copy of the unrevised information in our records. You may request deletion of your account by emailing biz@hashicorp.com. Please note that some information may remain in our records after your deletion of such information from your account. We may use any aggregated data derived from or incorporating your Personal Information after you update or delete it, but not in a manner that would identify you personally. +• You may be able to add, update, or delete information as explained in Section V above. When you update information, however, we may maintain a copy of the unrevised information in our records. You may request deletion of your account by emailing support@hashicorp.com. Please note that some information may remain in our records after your deletion of such information from your account. We may use any aggregated data derived from or incorporating your Personal Information after you update or delete it, but not in a manner that would identify you personally. VII. CHANGES TO THIS PRIVACY POLICY We may amend this Privacy Policy from time to time. Use of information we collect now is subject to the Privacy Policy in effect at the time such information is used. If we make changes in the way we use Personal Information, we will notify you by posting an announcement on our Website or sending you a message. You are bound by any changes to the Privacy Policy when you use the Services after such changes have been first posted. VIII. QUESTIONS OR CONCERNS -If you have any questions or concerns regarding our privacy policies, please send us a detailed message to biz@hashicorp.com, and we will try to resolve your concerns. +If you have any questions or concerns regarding our privacy policies, please send us a detailed message to support@hashicorp.com, and we will try to resolve your concerns. Effective Date: March, 1 2013 diff --git a/website/www/source/vmware/reseller.html.md b/website/www/source/vmware/reseller.html.md new file mode 100644 index 000000000..75f954f2b --- /dev/null +++ b/website/www/source/vmware/reseller.html.md @@ -0,0 +1,67 @@ +--- +layout: "inner" +--- + +# Reseller Information + +We are very happy to work with resellers for the Vagrant VMware provider. + +This page is intended to answer questions commonly +needed by resellers. If you are a reseller, +all the information required should be here. + +## Volume Pricing + +We do not offer reseller discounts. However, we do offer volume pricing +when purchasing more than 100 licenses. In that case, please email +sales@hashicorp.com to learn more. + +## Quote + +One time $79 purchase fee per seat. + +## Workstation vs. Fusion + +The VMware Workstation and Fusion licenses are separate. They are both +priced the same. + +## Purchase Orders + +Althought we prefer the self-serve purchase, you may email us a complete +PO (with credit card information included) to support@hashicorp.com. We +will then process your order. + +## Seats + +A single seat can be used on two computers (such as a desktop and a laptop) +for a single person. The license is valid forever with access to free +maintenance updates. + +## Upgrades + +Future major updates may require an upgrade fee. + +## Licenses + +After purchase, license files are generated and emailed to you. + +## Software EULA/Terms + +You can download a copy of the EULA [here](https://s3.amazonaws.com/hc-public/sales/EULA_standalone.pdf). + +## Company Details + +Business name: + + HashiCorp, Inc. + +Business website and support: + + hashicorp.com + support@hashicorp.com + +Our current registered business address is: + + 3120 23rd St + San Francisco, CA + 94110 USA diff --git a/website/www/source/vmware/terms-of-service.html.md b/website/www/source/vmware/terms-of-service.html.md index e2449d2a1..09dc46b86 100644 --- a/website/www/source/vmware/terms-of-service.html.md +++ b/website/www/source/vmware/terms-of-service.html.md @@ -8,7 +8,7 @@ PLEASE READ THESE TERMS OF USE (“AGREEMENT”) CAREFULLY BEFORE USING THE SERV 1. ACCESS TO THE SERVICES. The hashicorp.com and vagrantup.com website and domain name and any other linked pages, features, content, or application services (including without limitation any mobile application services) offered from time to time by Company in connection therewith (collectively, the “Website”) are owned and operated by Company. Subject to the terms and conditions of this Agreement, Company may offer to provide certain services, as described more fully on the Website, and that have been selected by you (together with the Website, the “Services”), solely for your own use, and not for the use or benefit of any third party. The term “Services” includes, without limitation, use of the Website, any service Company performs for you and the Content (as defined below) offered by Company on the Website. Company may change, suspend or discontinue the Services at any time, including the availability of any feature, database, or Content. Company may also impose limits on certain features and services or restrict your access to parts or all of the Services without notice or liability. Company reserves the right, in its sole discretion, to modify this Agreement at any time by posting a notice on the Website, or by sending you a notice. You shall be responsible for reviewing and becoming familiar with any such modifications. Your use of the Services following such notification constitutes your acceptance of the terms and conditions of this Agreement as modified. -Company does not knowingly collect or solicit personal information from anyone under the age of 13 or knowingly allow such persons to register for the Services. If you are under 13, please do not attempt to register for the Services or send any information about yourself to us, including your name, address, telephone number, or email address. No one under age 13 may provide any personal information to Company or on the Services. In the event that we learn that we have collected personal information from a child under age 13 without verification of parental consent, we will delete that information as quickly as possible. If you believe that we might have any information from or about a child under 13, please contact us at biz@hashicorp.com. +Company does not knowingly collect or solicit personal information from anyone under the age of 13 or knowingly allow such persons to register for the Services. If you are under 13, please do not attempt to register for the Services or send any information about yourself to us, including your name, address, telephone number, or email address. No one under age 13 may provide any personal information to Company or on the Services. In the event that we learn that we have collected personal information from a child under age 13 without verification of parental consent, we will delete that information as quickly as possible. If you believe that we might have any information from or about a child under 13, please contact us at support@hashicorp.com. You represent and warrant to Company that: (i) you are an individual (i.e., not a corporation) and you are of legal age to form a binding contract or have your parent’s permission to do so, and you are at least 13 years or age or older; (ii) all registration information you submit is accurate and truthful; and (iii) you will maintain the accuracy of such information. You also certify that you are legally permitted to use and access the Services and take full responsibility for the selection and use of and access to the Services. This Agreement is void where prohibited by law, and the right to access the Services is revoked in such jurisdictions. @@ -44,7 +44,7 @@ Your interactions with organizations and/or individuals found on or through the 13. ARBITRATION; GOVERNING LAW. This Agreement shall be governed by and construed in accordance with the laws of the State of California without regard to the conflict of laws provisions thereof. Any dispute arising from or relating to the subject matter of this Agreement shall be finally settled by arbitration in San Francisco County, California, using the English language in accordance with the Streamlined Arbitration Rules and Procedures of Judicial Arbitration and Mediation Services, Inc. (“JAMS”) then in effect, by one commercial arbitrator with substantial experience in resolving intellectual property and commercial contract disputes, who shall be selected from the appropriate list of JAMS arbitrators in accordance with the Streamlined Arbitration Rules and Procedures of JAMS. Judgment upon the award so rendered may be entered in a court having jurisdiction, or application may be made to such court for judicial acceptance of any award and an order of enforcement, as the case may be. Notwithstanding the foregoing, each party shall have the right to institute an action in a court of proper jurisdiction for injunctive or other equitable relief pending a final decision by the arbitrator. For all purposes of this Agreement, the parties consent to exclusive jurisdiction and venue in the United States Federal Courts located in the Northern District of California. -14. CONTACT. If you have any questions, complaints, or claims with respect to the Services, you may contact us at biz@hashicorp.com. +14. CONTACT. If you have any questions, complaints, or claims with respect to the Services, you may contact us at support@hashicorp.com. Effective: March 1, 2013 diff --git a/website/www/version.txt b/website/www/version.txt index 60abf8d5d..67ebdb246 100644 --- a/website/www/version.txt +++ b/website/www/version.txt @@ -1,4 +1,4 @@ This is just used to force a deploy on Heroku. Just update this number whenever you want: -10 +13