diff --git a/.gitignore b/.gitignore index b1274f4d0..eedfe7d70 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ # OS-specific .DS_Store +# Editor swapfiles +.*.sw? +*~ + # Vagrant stuff acceptance_config.yml boxes/* diff --git a/CHANGELOG.md b/CHANGELOG.md index c6d56fb98..892499cad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,22 +2,162 @@ FEATURES: + - **New Command: `vagrant powershell`**: For machines that support it, + this will open a PowerShell prompt. + - **New Command: `vagrant port`**: For machines that support it, this will + display the list of forwarded ports from the guest to the host. - **Linked Clones**: VirtualBox and VMware providers now support linked clones for very fast (millisecond) imports on up. [GH-4484] - **Snapshots**: The `vagrant snapshot` command can be used to checkpoint and restore point-in-time snapshots. - **IPv6 Private Networks**: Private networking now supports IPv6. This only works with VirtualBox and VMware at this point. [GH-6342] + - New provisioner: `ansible_local` to execute Ansible from the guest + machine. [GH-2103] + +BREAKING CHANGES: + + - The `ansible` provisioner now can override the effective ansible remote user + (i.e. `ansible_ssh_user` setting) to always correspond to the vagrant ssh + username. This change is enabled by default, but we expect this to affect + only a tiny number of people as it corresponds to the common usage. + If you however use multiple remote usernames in your Ansible plays, tasks, + or custom inventories, you can simply set the option `force_remote_user` to + false to make Vagrant behave the same as before. + - provisioners/salt: the "config_dir" option has been removed. It has no + effect in Vagrant 1.8. [GH-6073] IMPROVEMENTS: - core: allow removal of all box versions with `--all` flag [GH-3462] + - core: prune entries from global status on non-existent cwd [GH-6535] + - core: networking: allow specifying a DHCP IP [GH-6325] + - core: run provisioner cleanup tasks before powering off the VM [GH-6553] + - core: only run provisioner cleanup tasks if they're implemented [GH-6603] + This improves UX, but wasn't a bug before. + - command/plugin: Add `--plugin-clean-sources` flag to reset plugin install + sources, primarily for corp firewalls. [GH-4738] + - command/rsync-auto: SSH connection is cached for faster sync times [GH-6399] + - command/up: provisioners are run on suspend resume [GH-5815] + - communicators/ssh: allow specifying host environment variables to forward + to guests [GH-4132, GH-6562] + - communicators/winrm: Configurable execution time limit [GH-6213] + - providers/virtualbox: cache version lookup, which caused significant + slowdown on some Windows hosts [GH-6552] + - providers/virtualbox: add `public_address` capability for virtualbox + [GH-6583, GH-5978] + - provisioners/chef: perform cleanup tasks on the guest instead of the host + - provisioners/chef: automatically generate a node_name if one was not given + [GH-6555] + - provisioners/chef: install Chef automatically on Windows [GH-6557] + - provisioners/chef: allow the user to specify the Chef product (such as + the Chef Development Kit) [GH-6557] + - provisioners/chef: allow data_bags_path to be an array [GH-5988, GH-6561] + - provisioners/shell: Support interactive mode for elevated PowerShell + scripts [GH-6185] + - provisioners/shell: add `env` option [GH-6588, GH-6516] + - provisioners/ansible+ansible_local: add support for ansible-galaxy [GH-2718] + - provisioners/ansible+ansible_local: add support for group and host variables + in the generated inventory [GH-6619] + - provisioners/ansible+ansible_local: add support for alphanumeric patterns + for groups in the generated inventory [GH-3539] + - provisioners/ansible: add support for WinRM settings [GH-5086] + - provisioners/ansible: add new `force_remote_user` option to control whether + `ansible_ssh_user` parameter should be applied or not [GH-6348] + - provisioners/ansible: show a warning when running from a Windows Host [GH-5292] + - pushes/local-exec: add support for specifying script args [GH-6661, GH-6660] + - guests/slackware: add support for networking [GH-6514] BUG FIXES: + - core: Ctrl-C weirdness fixed where it would exit parent process + before Vagrant finished cleaning up [GH-6085] + - core: DHCP network configurations don't warn on IP addresses ending + in ".1" [GH-6150] + - core: only append `access_token` when it does not exist in the URL + [GH-6395, GH-6534] + - core: use the correct private key when packaging a box [GH-6406] + - core: fix crash when using invalid box checksum type [GH-6327] + - core: don't check for metadata if the download URL is not HTTP [GH-6540] + - core: don't make custom dotfile path if there is no Vagrantfile [GH-6542] + - core: more robust check for admin privs on Windows [GH-5616] + - core: properly detect when HTTP server doesn't support byte ranges and + retry from scratch [GH-4479] + - core: line numbers show properly in Vagrantfile syntax errors + on Windows [GH-6445] + - core: catch errors setting env vars on Windows [GH-6017] + - core: remove cached synced folders when they're removed from the + Vagrantfile [GH-6567] + - core: use case-insensitive comparison for box checksum validations + [GH-6648, GH-6650] + - commands/box: add command with `~` paths on Windows works [GH-5747] + - commands/box: the update command supports CA settings [GH-4473] + - commands/box: removing all versions and providers of a box will properly + clean all directories in `~/.vagrant.d/boxes` [GH-3570] + - commands/box: outdated global won't halt on metadata download failure [GH-6453] + - commands/login: respect environment variables in `vagrant login` command + [GH-6590, GH-6422] + - commands/package: when re-packaging a packaged box, preserve the + generated SSH key [GH-5780] + - commands/plugin: retry plugin install automatically a few times to + avoid network issues [GH-6097] + - commands/rdp: prefer `xfreerdp` if it is available on Linux [GH-6475] + - commands/up: the `--provision-with` flag works with provisioner names [GH-5981] + - communicator/ssh: fix potential crash case with PTY [GH-6225] + - communicator/ssh: escape IdentityFile path [GH-6428, GH-6589] - communicator/winrm: respect `boot_timeout` setting [GH-6229] + - communicator/winrm: execute scheduled tasks immediately on Windows XP + since elevation isn't required [GH-6195] + - communicator/winrm: Decouple default port forwarding rules for "winrm" and + "winrm-ssl" [GH-6581] + - communicator/winrm: Hide progress bars from PowerShell v5 [GH-6309] + - guests/arch: enable network device after setting it up [GH-5737] + - guests/darwin: advanced networking works with more NICs [GH-6386] + - guests/debian: graceful shutdown works properly with newer releases [GH-5986] + - guests/fedora: Preserve `localhost` entry when changing hostname [GH-6203] + - guests/fedora: Use dnf if it is available [GH-6288] + - guests/linux: when replacing a public SSH key, use POSIX-compliant + sed flags [GH-6565] + - guests/suse: DHCP network interfaces properly configured [GH-6502] + - hosts/slackware: Better detection of NFS [GH-6367] + - providers/hyper-v: support generation 2 VMs [GH-6372] + - providers/hyper-v: support VMs with more than one NIC [GH-4346] + - providers/hyper-v: check if user is in the Hyper-V admin group if + they're not a Windows admin [GH-6662] + - providers/virtualbox: ignore "Unknown" status bridge interfaces [GH-6061] + - providers/virtualbox: only fix ipv6 interfaces that are in use + [GH-6586, GH-6552] - provisioners/ansible: use quotes for the `ansible_ssh_private_key_file` value in the generated inventory [GH-6209] + - provisioners/ansible: use quotes when passing the private key files via + OpenSSH `-i` command line arguments [GH-6671] + - provisioners/ansible: don't show the `ansible-playbook` command when verbose + option is an empty string + - provisioners/chef: fix `nodes_path` for Chef Zero [GH-6025, GH-6049] + - provisioners/chef: do not error when the `node_name` is unset + [GH-6005, GH-6064, GH-6541] + - provisioners/chef: only force the formatter on Chef 11 or higher + [GH-6278, GH-6556] + - provisioners/chef: require `nodes_path` to be set for Chef Zero + [GH-6110, GH-6559] + - provisioners/puppet: apply provisioner uses correct default manifests + with environments. [GH-5987] + - provisioners/puppet: remove broken backticks [GH-6404] + - provisioners/puppet: find Puppet binary properly on Windows [GH-6259] + - provisioners/puppet-server: works with Puppet Collection 1 [GH-6389] + - provisioners/salt: call correct executables on Windows [GH-5999] + - provisioners/salt: log level and colorize works for masterless [GH-6474] + - push/local-exec: use subprocess on windows when fork does not exist + [GH-5307, GH-6563] + - push/heroku: use current branch [GH-6554] + - synced\_folders/rsync: on Windows, replace all paths with Cygwin + paths since all rsync implementations require this [GH-6160] + - synced\_folders/smb: use credentials files to allow for more characters + in password [GH-4230] + +PLUGIN AUTHOR CHANGES: + + - installer: Upgrade to Ruby 2.2.3 ## 1.7.4 (July 17, 2015) diff --git a/README.md b/README.md index a361da9b0..b03ca1f31 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Vagrant -* Website: [http://www.vagrantup.com](http://www.vagrantup.com) +* Website: [https://www.vagrantup.com/](https://www.vagrantup.com/) * Source: [https://github.com/mitchellh/vagrant](https://github.com/mitchellh/vagrant) * IRC: `#vagrant` on Freenode * Mailing list: [Google Groups](http://groups.google.com/group/vagrant-up) @@ -19,12 +19,12 @@ between Windows, Mac OS X, and Linux. ## Quick Start For the quick-start, we'll bring up a development machine on -[VirtualBox](http://www.virtualbox.org) because it is free and works +[VirtualBox](https://www.virtualbox.org/) because it is free and works on all major platforms. Vagrant can, however, work with almost any -system such as OpenStack, VMware, Docker, etc. +system such as [OpenStack] (https://www.openstack.org/), [VMware] (http://www.vmware.com/), [Docker] (https://docs.docker.com/), etc. First, make sure your development machine has -[VirtualBox](http://www.virtualbox.org) +[VirtualBox](https://www.virtualbox.org/) installed. After this, [download and install the appropriate Vagrant package for your OS](http://www.vagrantup.com/downloads). @@ -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](https://github.com/bundler/bundler) which can be installed with a simple `gem install bundler`. Afterwards, do the following: bundle install diff --git a/contrib/README.md b/contrib/README.md index c89c2ef56..bbdf84141 100644 --- a/contrib/README.md +++ b/contrib/README.md @@ -10,5 +10,7 @@ for each item will be kept below. * `emacs` - Contains a file for enabling Ruby syntax highlighting for `Vagrantfile`s in `emacs`. * `st` - Contains a `.sublime-settings` file for enabling Ruby syntax highlighting for `Vagrantfile`s in Sublime Text. +* `sudoers` - Contains the pieces of `/etc/sudoers` configuration to avoid password entry when + starting machines. * `vim` - Contains a `.vim` file for enabling Ruby syntax highlighting - for `Vagrantfile`s in `vim`. \ No newline at end of file + for `Vagrantfile`s in `vim`. diff --git a/contrib/sudoers/linux-fedora b/contrib/sudoers/linux-fedora index f2d64b66d..5bec89189 100644 --- a/contrib/sudoers/linux-fedora +++ b/contrib/sudoers/linux-fedora @@ -2,5 +2,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 +Cmnd_Alias VAGRANT_EXPORTS_REMOVE = /bin/sed -r -e * d -ibak /*/exports +Cmnd_Alias VAGRANT_EXPORTS_REMOVE_2 = /bin/cp /*/exports /etc/exports +%vagrant ALL=(root) NOPASSWD: VAGRANT_EXPORTS_ADD, VAGRANT_NFSD_CHECK, VAGRANT_NFSD_START, VAGRANT_NFSD_APPLY, VAGRANT_EXPORTS_REMOVE, VAGRANT_EXPORTS_REMOVE_2 diff --git a/contrib/sudoers/linux-suse b/contrib/sudoers/linux-suse new file mode 100644 index 000000000..30658af3f --- /dev/null +++ b/contrib/sudoers/linux-suse @@ -0,0 +1,7 @@ +Cmnd_Alias VAGRANT_EXPORTS_ADD = /usr/bin/tee -a /etc/exports +Cmnd_Alias VAGRANT_NFSD_CHECK = /sbin/service nfsserver status +Cmnd_Alias VAGRANT_NFSD_START = /sbin/service nfsserver start +Cmnd_Alias VAGRANT_NFSD_APPLY = /usr/sbin/exportfs -ar +Cmnd_Alias VAGRANT_EXPORTS_REMOVE = /usr/bin/sed -r -e * d -ibak /*/exports +Cmnd_Alias VAGRANT_EXPORTS_REMOVE_2 = /usr/bin/cp /*/exports /etc/exports +%vagrant ALL=(root) NOPASSWD: VAGRANT_EXPORTS_ADD, VAGRANT_NFSD_CHECK, VAGRANT_NFSD_START, VAGRANT_NFSD_APPLY, VAGRANT_EXPORTS_REMOVE, VAGRANT_EXPORTS_REMOVE_2 diff --git a/contrib/sudoers/linux-ubuntu b/contrib/sudoers/linux-ubuntu index c4e786cf3..4e2cd8bde 100644 --- a/contrib/sudoers/linux-ubuntu +++ b/contrib/sudoers/linux-ubuntu @@ -4,5 +4,6 @@ Cmnd_Alias VAGRANT_EXPORTS_ADD = /usr/bin/tee -a /etc/exports Cmnd_Alias VAGRANT_NFSD_CHECK = /etc/init.d/nfs-kernel-server status Cmnd_Alias VAGRANT_NFSD_START = /etc/init.d/nfs-kernel-server start Cmnd_Alias VAGRANT_NFSD_APPLY = /usr/sbin/exportfs -ar -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 +Cmnd_Alias VAGRANT_EXPORTS_REMOVE = /bin/sed -r -e * d -ibak /*/exports +Cmnd_Alias VAGRANT_EXPORTS_REMOVE_2 = /bin/cp /*/exports /etc/exports +%sudo ALL=(root) NOPASSWD: VAGRANT_EXPORTS_ADD, VAGRANT_NFSD_CHECK, VAGRANT_NFSD_START, VAGRANT_NFSD_APPLY, VAGRANT_EXPORTS_REMOVE, VAGRANT_EXPORTS_REMOVE_2 diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 6aec780ee..b87bf46b6 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -381,7 +381,9 @@ module Vagrant @logger.info("URL is a file or protocol not found and assuming file.") file_path = File.expand_path(url) file_path = Util::Platform.cygwin_windows_path(file_path) - url = "file:#{file_path}" + file_path = file_path.gsub("\\", "/") + file_path = "/#{file_path}" if !file_path.start_with?("/") + url = "file://#{file_path}" end # If the temporary path exists, verify it is not too old. If its @@ -491,6 +493,12 @@ module Vagrant end end + # If this isn't HTTP, then don't do the HEAD request + if !uri.scheme.downcase.start_with?("http") + @logger.info("not checking metadata since box URI isn't HTTP") + return false + end + output = d.head match = output.scan(/^Content-Type: (.+?)$/i).last return false if !match @@ -507,14 +515,14 @@ module Vagrant Digest::SHA2 else raise Errors::BoxChecksumInvalidType, - type: env[:box_checksum_type].to_s + type: checksum_type.to_s end @logger.info("Validating checksum with #{checksum_klass}") @logger.info("Expected checksum: #{checksum}") actual = FileChecksum.new(path, checksum_klass).checksum - if actual != checksum + if actual.casecmp(checksum) != 0 raise Errors::BoxChecksumMismatch, actual: actual, expected: checksum diff --git a/lib/vagrant/action/builtin/box_check_outdated.rb b/lib/vagrant/action/builtin/box_check_outdated.rb index cee8c6877..d39afd272 100644 --- a/lib/vagrant/action/builtin/box_check_outdated.rb +++ b/lib/vagrant/action/builtin/box_check_outdated.rb @@ -37,13 +37,23 @@ module Vagrant end constraints = machine.config.vm.box_version + # Have download options specified in the environment override + # options specified for the machine. + download_options = { + ca_cert: env[:ca_cert] || machine.config.vm.box_download_ca_cert, + ca_path: env[:ca_path] || machine.config.vm.box_download_ca_path, + client_cert: env[:client_cert] || + machine.config.vm.box_download_client_cert, + insecure: !env[:insecure].nil? ? + env[:insecure] : machine.config.vm.box_download_insecure + } env[:ui].output(I18n.t( "vagrant.box_outdated_checking_with_refresh", name: box.name)) update = nil begin - update = box.has_update?(constraints) + update = box.has_update?(constraints, download_options: download_options) rescue Errors::BoxMetadataDownloadError => e env[:ui].warn(I18n.t( "vagrant.box_outdated_metadata_download_error", diff --git a/lib/vagrant/action/builtin/box_remove.rb b/lib/vagrant/action/builtin/box_remove.rb index 5d18ed704..cf80542cb 100644 --- a/lib/vagrant/action/builtin/box_remove.rb +++ b/lib/vagrant/action/builtin/box_remove.rb @@ -111,6 +111,7 @@ module Vagrant provider: box.provider, version: box.version)) box.destroy! + env[:box_collection].clean(box.name) # Passes on the removed box to the rest of the middleware chain env[:box_removed] = box diff --git a/lib/vagrant/action/builtin/handle_forwarded_port_collisions.rb b/lib/vagrant/action/builtin/handle_forwarded_port_collisions.rb index b53ba35c2..05e8edfa3 100644 --- a/lib/vagrant/action/builtin/handle_forwarded_port_collisions.rb +++ b/lib/vagrant/action/builtin/handle_forwarded_port_collisions.rb @@ -128,7 +128,7 @@ module Vagrant port_checker[repaired_port] || lease_check(repaired_port) if in_use - @logger.info("Reparied port also in use: #{repaired_port}. Trying another...") + @logger.info("Repaired port also in use: #{repaired_port}. Trying another...") next end @@ -156,8 +156,6 @@ module Vagrant new_port: repaired_port.to_s)) end end - - @app.call(env) end def lease_check(port) diff --git a/lib/vagrant/action/builtin/mixin_synced_folders.rb b/lib/vagrant/action/builtin/mixin_synced_folders.rb index fb0abcce2..08fa867a9 100644 --- a/lib/vagrant/action/builtin/mixin_synced_folders.rb +++ b/lib/vagrant/action/builtin/mixin_synced_folders.rb @@ -76,6 +76,16 @@ module Vagrant if opts[:merge] existing = cached_synced_folders(machine) if existing + if opts[:vagrantfile] + # Go through and find any cached that were from the + # Vagrantfile itself. We remove those if it was requested. + existing.each do |impl, fs| + fs.each do |id, data| + fs.delete(id) if data[:__vagrantfile] + end + end + end + folders.each do |impl, fs| existing[impl] ||= {} fs.each do |id, data| @@ -101,7 +111,12 @@ module Vagrant return cached_synced_folders(machine) if opts[:cached] config = opts[:config] - config ||= machine.config.vm + root = false + if !config + config = machine.config.vm + root = true + end + config_folders = config.synced_folders folders = {} @@ -131,9 +146,17 @@ module Vagrant end end + # Get the data to store + data = data.dup + if root + # If these are the root synced folders (attached directly) + # to the Vagrantfile, then we mark it as such. + data[:__vagrantfile] = true + end + # Keep track of this shared folder by the implementation. folders[impl] ||= {} - folders[impl][id] = data.dup + folders[impl][id] = data end # If we have folders with the "default" key, then determine the diff --git a/lib/vagrant/action/builtin/provisioner_cleanup.rb b/lib/vagrant/action/builtin/provisioner_cleanup.rb index 3aab56c6e..ead72268c 100644 --- a/lib/vagrant/action/builtin/provisioner_cleanup.rb +++ b/lib/vagrant/action/builtin/provisioner_cleanup.rb @@ -13,8 +13,9 @@ module Vagrant def initialize(app, env, place=nil) @app = app @logger = Log4r::Logger.new("vagrant::action::builtin::provision_cleanup") - @place ||= :after - @place = @place.to_sym + + place ||= :after + @place = place.to_sym end def call(env) @@ -31,10 +32,20 @@ module Vagrant # Ask the provisioners to modify the configuration if needed provisioner_instances(env).each do |p, _| - env[:ui].info(I18n.t( - "vagrant.provisioner_cleanup", - name: type_map[p].to_s)) - p.cleanup + name = type_map[p].to_s + + # Check if the subclass defined a cleanup method. The parent + # provisioning class defines a `cleanup` method, so we cannot use + # `respond_to?` here. Instead, we have to check if _this_ instance + # defines a cleanup task. + if p.public_methods(false).include?(:cleanup) + env[:ui].info(I18n.t("vagrant.provisioner_cleanup", + name: name, + )) + p.cleanup + else + @logger.debug("Skipping cleanup tasks for `#{name}' - not defined") + end end end end diff --git a/lib/vagrant/action/builtin/synced_folders.rb b/lib/vagrant/action/builtin/synced_folders.rb index 230676d66..b56c212db 100644 --- a/lib/vagrant/action/builtin/synced_folders.rb +++ b/lib/vagrant/action/builtin/synced_folders.rb @@ -23,6 +23,7 @@ module Vagrant config: env[:synced_folders_config], } + @logger.info("SyncedFolders loading from cache: #{opts[:cached]}") folders = synced_folders(env[:machine], **opts) original_folders = folders @@ -121,8 +122,11 @@ module Vagrant save_synced_folders(env[:machine], all) else + save_opts = { merge: true } + save_opts[:vagrantfile] = true if !opts[:config] + # Save the synced folders - save_synced_folders(env[:machine], original_folders, merge: true) + save_synced_folders(env[:machine], original_folders, **save_opts) end end end diff --git a/lib/vagrant/action/general/package.rb b/lib/vagrant/action/general/package.rb index 58991222c..beb6f05fc 100644 --- a/lib/vagrant/action/general/package.rb +++ b/lib/vagrant/action/general/package.rb @@ -30,7 +30,7 @@ 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, file_name:file_name if File.exist?(tar_path) raise Errors::PackageRequiresDirectory if !env["package.directory"] || @@ -113,6 +113,21 @@ module Vagrant # If we don't have a generated private key, we do nothing path = @env[:machine].data_dir.join("private_key") + if !path.file? + # If we have a private key that was copied into this box, + # then we copy that. This is a bit of a heuristic and can be a + # security risk if the key is named the correct thing, but + # we'll take that risk for dev environments. + (@env[:machine].config.ssh.private_key_path || []).each do |p| + # If we have the correctly named key, copy it + if File.basename(p) == "vagrant_private_key" + path = Pathname.new(p) + break + end + end + end + + # If we still have no matching key, do nothing return if !path.file? # Copy it into our box directory diff --git a/lib/vagrant/box.rb b/lib/vagrant/box.rb index ce829870a..7b0c7da3a 100644 --- a/lib/vagrant/box.rb +++ b/lib/vagrant/box.rb @@ -115,8 +115,9 @@ module Vagrant # Loads the metadata URL and returns the latest metadata associated # with this box. # + # @param [Hash] download_options Options to pass to the downloader. # @return [BoxMetadata] - def load_metadata + def load_metadata(**download_options) tf = Tempfile.new("vagrant") tf.close @@ -127,7 +128,7 @@ module Vagrant url = "file:#{url}" end - opts = { headers: ["Accept: application/json"] } + opts = { headers: ["Accept: application/json"] }.merge(download_options) Util::Downloader.new(url, tf.path, **opts).download! BoxMetadata.new(File.open(tf.path, "r")) rescue Errors::DownloaderError => e @@ -148,7 +149,7 @@ module Vagrant # satisfy. If nil, the version constrain defaults to being a # larger version than this box. # @return [Array] - def has_update?(version=nil) + def has_update?(version=nil, download_options: {}) if !@metadata_url raise Errors::BoxUpdateNoMetadata, name: @name end @@ -156,7 +157,7 @@ module Vagrant version += ", " if version version ||= "" version += "> #{@version}" - md = self.load_metadata + md = self.load_metadata(download_options) newer = md.version(version, provider: @provider) return nil if !newer diff --git a/lib/vagrant/box_collection.rb b/lib/vagrant/box_collection.rb index a7599f7c2..6e83ad4ee 100644 --- a/lib/vagrant/box_collection.rb +++ b/lib/vagrant/box_collection.rb @@ -12,7 +12,9 @@ module Vagrant # for accessing/finding individual boxes, adding new boxes, or deleting # boxes. class BoxCollection - TEMP_PREFIX = "vagrant-box-add-temp-" + TEMP_PREFIX = "vagrant-box-add-temp-".freeze + VAGRANT_SLASH = "-VAGRANTSLASH-".freeze + VAGRANT_COLON = "-VAGRANTCOLON-".freeze # The directory where the boxes in this collection are stored. # @@ -346,6 +348,14 @@ module Vagrant end end + # Cleans the directory for a box by removing the folders that are + # empty. + def clean(name) + return false if exists?(name) + path = File.join(directory, dir_name(name)) + FileUtils.rm_rf(path) + end + protected # Returns the directory name for the box of the given name. @@ -354,16 +364,16 @@ module Vagrant # @return [String] def dir_name(name) name = name.dup - name.gsub!(":", "-VAGRANTCOLON-") if Util::Platform.windows? - name.gsub!("/", "-VAGRANTSLASH-") + name.gsub!(":", VAGRANT_COLON) if Util::Platform.windows? + name.gsub!("/", VAGRANT_SLASH) name end # Returns the directory name for the box cleaned up def undir_name(name) name = name.dup - name.gsub!("-VAGRANTCOLON-", ":") - name.gsub!("-VAGRANTSLASH-", "/") + name.gsub!(VAGRANT_COLON, ":") + name.gsub!(VAGRANT_SLASH, "/") name end @@ -440,5 +450,10 @@ module Vagrant ensure dir.rmtree if dir.exist? end + + # Checks if a box with a given name exists. + def exists?(box_name) + all.any? { |box| box.first.eql?(box_name) } + end end end diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index 05867da15..fe9699ef4 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -7,6 +7,7 @@ require "bundler" require_relative "shared_helpers" require_relative "version" +require_relative "util/safe_env" module Vagrant # This class manages Vagrant's interaction with Bundler. Vagrant uses @@ -42,6 +43,9 @@ module Vagrant yield end end + + # Configure Bundler to retry + ::Bundler.settings[:retry] = 3 end # Initializes Bundler and the various gem paths so that we can begin @@ -68,12 +72,15 @@ module Vagrant # we add all our plugin dependencies. @gemfile = build_gemfile(plugins) - # Set the environmental variables for Bundler - ENV["BUNDLE_APP_CONFIG"] = @appconfigpath - ENV["BUNDLE_CONFIG"] = @configfile.path - ENV["BUNDLE_GEMFILE"] = @gemfile.path - ENV["GEM_PATH"] = - "#{bundle_path}#{::File::PATH_SEPARATOR}#{@gem_path}" + Util::SafeEnv.change_env do |env| + # Set the environmental variables for Bundler + env["BUNDLE_APP_CONFIG"] = @appconfigpath + env["BUNDLE_CONFIG"] = @configfile.path + env["BUNDLE_GEMFILE"] = @gemfile.path + env["GEM_PATH"] = + "#{bundle_path}#{::File::PATH_SEPARATOR}#{@gem_path}" + end + Gem.clear_paths end @@ -178,11 +185,6 @@ module Vagrant f = File.open(Tempfile.new("vagrant").path + "2", "w+") f.tap do |gemfile| - if !sources.include?("http://rubygems.org") - gemfile.puts(%Q[source "https://rubygems.org"]) - end - - gemfile.puts(%Q[source "http://gems.hashicorp.com"]) sources.each do |source| next if source == "" gemfile.puts(%Q[source "#{source}"]) diff --git a/lib/vagrant/config/loader.rb b/lib/vagrant/config/loader.rb index ef4abe4bd..2bf011231 100644 --- a/lib/vagrant/config/loader.rb +++ b/lib/vagrant/config/loader.rb @@ -221,7 +221,12 @@ module Vagrant line = "(unknown)" if e.backtrace && e.backtrace[0] - line = e.backtrace[0].split(":")[1] + e.backtrace[0].split(":").each do |part| + if part =~ /\d+/ + line = part.to_i + break + end + end end # Report the generic exception diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 88779a2e9..e98c8f397 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -832,7 +832,7 @@ module Vagrant # This creates the local data directory and show an error if it # couldn't properly be created. - def setup_local_data_path + def setup_local_data_path(force=false) if @local_data_path.nil? @logger.warn("No local data path is set. Local data cannot be stored.") return @@ -847,6 +847,9 @@ module Vagrant upgrade_v1_dotfile(@local_data_path) end + # If we don't have a root path, we don't setup anything + return if !force && root_path.nil? + begin @logger.debug("Creating: #{@local_data_path}") FileUtils.mkdir_p(@local_data_path) @@ -965,7 +968,7 @@ module Vagrant # Now, we create the actual local data directory. This should succeed # this time since we renamed the old conflicting V1. - setup_local_data_path + setup_local_data_path(true) if json_data["active"] @logger.debug("Upgrading to V2 style for each active VM") diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index c76f3cbe9..057d6a823 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -108,14 +108,6 @@ module Vagrant error_key(:active_machine_with_different_provider) end - class AnsibleFailed < VagrantError - error_key(:ansible_failed) - end - - class AnsiblePlaybookAppNotFound < VagrantError - error_key(:ansible_playbook_app_not_found) - end - class BatchMultiError < VagrantError error_key(:batch_multi_error) end @@ -248,6 +240,10 @@ module Vagrant error_key(:bundler_error) end + class CantReadMACAddresses < VagrantError + error_key(:cant_read_mac_addresses) + end + class CapabilityHostExplicitNotDetected < VagrantError error_key(:capability_host_explicit_not_detected) end @@ -348,6 +344,10 @@ module Vagrant error_key(:downloader_interrupted) end + class EnvInval < VagrantError + error_key(:env_inval) + end + class EnvironmentNonExistentCWD < VagrantError error_key(:environment_non_existent_cwd) end @@ -408,8 +408,8 @@ module Vagrant error_key(:linux_nfs_mount_failed) end - class LinuxRDesktopNotFound < VagrantError - error_key(:linux_rdesktop_not_found) + class LinuxRDPClientNotFound < VagrantError + error_key(:linux_rdp_client_not_found) end class LocalDataDirectoryNotAccessible < VagrantError @@ -524,6 +524,10 @@ module Vagrant error_key(:provider_cant_install) end + class ProviderChecksumMismatch < VagrantError + error_key(:provider_checksum_mismatch) + end + class ProviderInstallFailed < VagrantError error_key(:provider_install_failed) end diff --git a/lib/vagrant/machine.rb b/lib/vagrant/machine.rb index 07ec0ac94..f9ba4bcdf 100644 --- a/lib/vagrant/machine.rb +++ b/lib/vagrant/machine.rb @@ -116,6 +116,9 @@ module Vagrant # XXX: This is temporary. This will be removed very soon. if base @id = name + + # For base setups, we don't want to insert the key + @config.ssh.insert_key = false else reload end @@ -141,6 +144,10 @@ module Vagrant if state.id == MachineState::NOT_CREATED_ID self.id = nil end + + # Output a bunch of information about this machine in + # machine-readable format in case someone is listening. + @ui.machine("metadata", "provider", provider_name) end # This calls an action on the provider. The provider may or may not @@ -188,7 +195,10 @@ module Vagrant end # Call the action - action_raw(name, callable, extra_env) + ui.machine("action", name.to_s, "start") + action_result = action_raw(name, callable, extra_env) + ui.machine("action", name.to_s, "end") + action_result end rescue Errors::EnvironmentLockedError raise Errors::MachineActionLockedError, @@ -436,6 +446,7 @@ module Vagrant # We also set some fields that are purely controlled by Varant info[:forward_agent] = @config.ssh.forward_agent info[:forward_x11] = @config.ssh.forward_x11 + info[:forward_env] = @config.ssh.forward_env info[:ssh_command] = @config.ssh.ssh_command if @config.ssh.ssh_command diff --git a/lib/vagrant/machine_index.rb b/lib/vagrant/machine_index.rb index 6e8fe0e3a..1af574b33 100644 --- a/lib/vagrant/machine_index.rb +++ b/lib/vagrant/machine_index.rb @@ -78,7 +78,7 @@ module Vagrant @machines.delete(entry.id) unlocked_save - # Release acccess on this machine + # Release access on this machine unlocked_release(entry.id) end end diff --git a/lib/vagrant/plugin/v2/command.rb b/lib/vagrant/plugin/v2/command.rb index eeb580a29..f0b6c4415 100644 --- a/lib/vagrant/plugin/v2/command.rb +++ b/lib/vagrant/plugin/v2/command.rb @@ -133,8 +133,17 @@ module Vagrant # machine in that environment. We silence warnings here because # Vagrantfiles often have constants, so people would otherwise # constantly (heh) get "already initialized constant" warnings. - env = entry.vagrant_env( - @env.home_path, ui_class: @env.ui_class) + begin + env = entry.vagrant_env( + @env.home_path, ui_class: @env.ui_class) + rescue Vagrant::Errors::EnvironmentNonExistentCWD + # This means that this environment working directory + # no longer exists, so delete this entry. + entry = @env.machine_index.get(name.to_s) + @env.machine_index.delete(entry) if entry + raise + end + next env.machine(entry.name.to_sym, entry.provider.to_sym) end diff --git a/lib/vagrant/ui.rb b/lib/vagrant/ui.rb index c610643a9..75802352c 100644 --- a/lib/vagrant/ui.rb +++ b/lib/vagrant/ui.rb @@ -104,8 +104,8 @@ module Vagrant end [:detail, :warn, :error, :info, :output, :success].each do |method| - define_method(method) do |message, *opts| - machine("ui", method.to_s, message) + define_method(method) do |message, *args, **opts| + machine("ui", method.to_s, message, *args, **opts) end end @@ -123,9 +123,12 @@ module Vagrant data[i].gsub!("\r", "\\r") end - @lock.synchronize do - safe_puts("#{Time.now.utc.to_i},#{target},#{type},#{data.join(",")}") - end + # Avoid locks in a trap context introduced from Ruby 2.0 + Thread.new do + @lock.synchronize do + safe_puts("#{Time.now.utc.to_i},#{target},#{type},#{data.join(",")}") + end + end.join end end @@ -156,7 +159,7 @@ 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.windows? # Setup the options so that the new line is suppressed opts ||= {} @@ -277,6 +280,9 @@ module Vagrant opts[:bold] = #{method.inspect} != :detail && \ #{method.inspect} != :ask end + if !opts.key?(:target) + opts[:target] = @prefix + end @ui.#{method}(format_message(#{method.inspect}, message, **opts), *args, **opts) end CODE diff --git a/lib/vagrant/util/downloader.rb b/lib/vagrant/util/downloader.rb index edaf99e85..03e623897 100644 --- a/lib/vagrant/util/downloader.rb +++ b/lib/vagrant/util/downloader.rb @@ -138,11 +138,14 @@ module Vagrant # If we already retried, raise it. raise if retried + @logger.error("Exit code: #{e.extra_data[:code]}") + # If its any error other than 33, it is an error. - raise if e.extra_data[:exit_code].to_i != 33 + raise if e.extra_data[:code].to_i != 33 # Exit code 33 means that the server doesn't support ranges. # In this case, try again without resume. + @logger.error("Error is server doesn't support byte ranges. Retrying from scratch.") @continue = false retried = true retry diff --git a/lib/vagrant/util/platform.rb b/lib/vagrant/util/platform.rb index 706445c3b..017a6e558 100644 --- a/lib/vagrant/util/platform.rb +++ b/lib/vagrant/util/platform.rb @@ -10,10 +10,17 @@ module Vagrant class Platform class << self def cygwin? + # Installer detects Cygwin return true if ENV["VAGRANT_DETECTED_OS"] && ENV["VAGRANT_DETECTED_OS"].downcase.include?("cygwin") - platform.include?("cygwin") + # Ruby running in Cygwin + return true if platform.include?("cygwin") + + # Heuristic. If the path contains Cygwin, we just assume we're + # in Cygwin. It is generally a safe bet. + path = ENV["PATH"] || "" + return path.include?("cygwin") end [:darwin, :bsd, :freebsd, :linux, :solaris].each do |type| @@ -45,10 +52,32 @@ module Vagrant # detect-if-running-with-administrator-privileges-under-windows-xp begin Win32::Registry::HKEY_USERS.open("S-1-5-19") {} - return true rescue Win32::Registry::Error return false end + + # If we made it this far then we try a fallback approach + # since the above doesn't seem to be bullet proof. See GH-5616 + (`reg query HKU\\S-1-5-19 2>&1` =~ /ERROR/).nil? + end + + # Checks if the user running Vagrant on Windows is a member of the + # "Hyper-V Administrators" group. + # + # From: https://support.microsoft.com/en-us/kb/243330 + # SID: S-1-5-32-578 + # Name: BUILTIN\Hyper-V Administrators + # + # @return [Boolean] + def windows_hyperv_admin? + begin + username = ENV["USERNAME"] + process = Subprocess.execute("net", "localgroup", "Hyper-V Administrators") + output = process.stdout.chomp + return output.include?(username) + rescue Errors::CommandUnavailableWindows + return false + end end # This takes any path and converts it from a Windows path to a @@ -114,6 +143,14 @@ module Vagrant path = Pathname.new(File.expand_path(path)) if path.exist? && !fs_case_sensitive? + # If the path contains a Windows short path, then we attempt to + # expand. The require below is embedded here since it requires + # windows to work. + if windows? && path.to_s =~ /~\d(\/|\\)/ + require_relative "windows_path" + path = Pathname.new(WindowsPath.longname(path.to_s)) + end + # Build up all the parts of the path original = [] while !path.root? diff --git a/lib/vagrant/util/presence.rb b/lib/vagrant/util/presence.rb new file mode 100644 index 000000000..8032bf6c1 --- /dev/null +++ b/lib/vagrant/util/presence.rb @@ -0,0 +1,45 @@ +module Vagrant + module Util + module Presence + extend self + + # Determines if the given object is "present". A String is considered + # present if the stripped contents are not empty. An Array/Hash is + # considered present if they have a length of more than 1. "true" is + # always present and `false` and `nil` are always not present. Any other + # object is considered to be present. + # + # @return [true, false] + def present?(obj) + case obj + when String + !obj.strip.empty? + when Symbol + !obj.to_s.strip.empty? + when Array + !obj.compact.empty? + when Hash + !obj.empty? + when TrueClass, FalseClass + obj + when NilClass + false + when Object + true + end + end + + # Returns the presence of the object. If the object is {present?}, it is + # returned. Otherwise `false` is returned. + # + # @return [Object, false] + def presence(obj) + if present?(obj) + obj + else + false + end + end + end + end +end diff --git a/lib/vagrant/util/safe_env.rb b/lib/vagrant/util/safe_env.rb new file mode 100644 index 000000000..8d7d8baf1 --- /dev/null +++ b/lib/vagrant/util/safe_env.rb @@ -0,0 +1,14 @@ +module Vagrant + module Util + class SafeEnv + # This yields an environment hash to change and catches any issues + # while changing the environment variables and raises a helpful error + # to end users. + def self.change_env + yield ENV + rescue Errno::EINVAL + raise Errors::EnvInval + end + end + end +end diff --git a/lib/vagrant/util/ssh.rb b/lib/vagrant/util/ssh.rb index 20afcdb04..f5d6b916d 100644 --- a/lib/vagrant/util/ssh.rb +++ b/lib/vagrant/util/ssh.rb @@ -138,6 +138,10 @@ module Vagrant command_options += ["-o", "ProxyCommand=#{ssh_info[:proxy_command]}"] end + if ssh_info[:forward_env] + command_options += ["-o", "SendEnv=#{ssh_info[:forward_env].join(" ")}"] + end + # Configurables -- extra_args should always be last due to the way the # ssh args parser works. e.g. if the user wants to use the -t option, # any shell command(s) she'd like to run on the remote server would @@ -172,6 +176,14 @@ module Vagrant LOGGER.info("Executing SSH in subprocess: #{ssh} #{command_options.inspect}") process = ChildProcess.build(ssh, *command_options) process.io.inherit! + + # Forward configured environment variables. + if ssh_info[:forward_env] + ssh_info[:forward_env].each do |key| + process.environment[key] = ENV[key] + end + end + process.start process.wait return process.exit_code diff --git a/lib/vagrant/util/windows_path.rb b/lib/vagrant/util/windows_path.rb new file mode 100644 index 000000000..d2ea251d5 --- /dev/null +++ b/lib/vagrant/util/windows_path.rb @@ -0,0 +1,38 @@ +require "fiddle/import" + +module Vagrant + module Util + module WindowsPath + module API + extend Fiddle::Importer + dlload 'kernel32.dll' + extern("int GetLongPathNameA(char*, char*, int)", :stdcall) + end + + # Converts a Windows shortname to a long name. This only works + # for ASCII paths currently and doesn't use the wide character + # support. + def self.longname(name) + # We loop over the API call in case we didn't allocate enough + # buffer space. In general it is usually enough. + bufferlen = 250 + buffer = nil + while true + buffer = ' ' * bufferlen + len = API.GetLongPathNameA(name.to_s, buffer, buffer.size) + if bufferlen < len + # If the length returned is larger than our buffer length, + # it is the API telling us it needs more space. Allocate it + # and retry. + bufferlen = len + continue + end + + break + end + + return buffer.rstrip.chomp("\0") + end + end + end +end diff --git a/plugins/commands/box/command/add.rb b/plugins/commands/box/command/add.rb index 114601024..5545ea215 100644 --- a/plugins/commands/box/command/add.rb +++ b/plugins/commands/box/command/add.rb @@ -1,9 +1,13 @@ require 'optparse' +require_relative 'download_mixins' + module VagrantPlugins module CommandBox module Command class Add < Vagrant.plugin("2", :command) + include DownloadMixins + def execute options = {} @@ -21,22 +25,7 @@ module VagrantPlugins options[:force] = f end - o.on("--insecure", "Do not validate SSL certificates") do |i| - options[:insecure] = i - end - - o.on("--cacert FILE", String, "CA certificate for SSL download") do |c| - options[:ca_cert] = c - end - - o.on("--capath DIR", String, "CA certificate directory for SSL download") do |c| - options[:ca_path] = c - end - - o.on("--cert FILE", String, - "A client SSL cert, if needed") do |c| - options[:client_cert] = c - end + build_download_options(o, options) 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 @@ -97,7 +86,7 @@ module VagrantPlugins box_force: options[:force], box_download_ca_cert: options[:ca_cert], box_download_ca_path: options[:ca_path], - box_download_client_cert: options[:client_cert], + box_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/box/command/download_mixins.rb b/plugins/commands/box/command/download_mixins.rb new file mode 100644 index 000000000..eda31b6bf --- /dev/null +++ b/plugins/commands/box/command/download_mixins.rb @@ -0,0 +1,29 @@ +module VagrantPlugins + module CommandBox + module DownloadMixins + # This adds common download command line flags to the given + # OptionParser, storing the result in the `options` dictionary. + # + # @param [OptionParser] parser + # @param [Hash] options + def build_download_options(parser, options) + # Add the options + parser.on("--insecure", "Do not validate SSL certificates") do |i| + options[:insecure] = i + end + + parser.on("--cacert FILE", String, "CA certificate for SSL download") do |c| + options[:ca_cert] = c + end + + parser.on("--capath DIR", String, "CA certificate directory for SSL download") do |c| + options[:ca_path] = c + end + + parser.on("--cert FILE", String, "A client SSL cert, if needed") do |c| + options[:client_cert] = c + end + end + end + end +end diff --git a/plugins/commands/box/command/outdated.rb b/plugins/commands/box/command/outdated.rb index a686fa496..4fd6e5cf3 100644 --- a/plugins/commands/box/command/outdated.rb +++ b/plugins/commands/box/command/outdated.rb @@ -1,11 +1,16 @@ require 'optparse' +require_relative 'download_mixins' + module VagrantPlugins module CommandBox module Command class Outdated < Vagrant.plugin("2", :command) + include DownloadMixins + def execute options = {} + download_options = {} opts = OptionParser.new do |o| o.banner = "Usage: vagrant box outdated [options]" @@ -20,6 +25,8 @@ module VagrantPlugins o.on("--global", "Check all boxes installed") do |g| options[:global] = g end + + build_download_options(o, download_options) end argv = parse_options(opts) @@ -27,7 +34,7 @@ module VagrantPlugins # If we're checking the boxes globally, then do that. if options[:global] - outdated_global + outdated_global(download_options) return 0 end @@ -37,11 +44,11 @@ module VagrantPlugins box_outdated_refresh: true, box_outdated_success_ui: true, machine: machine, - }) + }.merge(download_options)) end end - def outdated_global + def outdated_global(download_options) boxes = {} @env.boxes.all.reverse.each do |name, version, provider| next if boxes[name] @@ -58,8 +65,8 @@ module VagrantPlugins md = nil begin - md = box.load_metadata - rescue Vagrant::Errors::DownloaderError => e + md = box.load_metadata(download_options) + rescue Vagrant::Errors::BoxMetadataDownloadError => e @env.ui.error(I18n.t( "vagrant.box_outdated_metadata_error", name: box.name, diff --git a/plugins/commands/box/command/update.rb b/plugins/commands/box/command/update.rb index 5633b0aaf..67d07bcc0 100644 --- a/plugins/commands/box/command/update.rb +++ b/plugins/commands/box/command/update.rb @@ -1,11 +1,16 @@ require 'optparse' +require_relative 'download_mixins' + module VagrantPlugins module CommandBox module Command class Update < Vagrant.plugin("2", :command) + include DownloadMixins + def execute options = {} + download_options = {} opts = OptionParser.new do |o| o.banner = "Usage: vagrant box update [options]" @@ -27,21 +32,23 @@ module VagrantPlugins o.on("--provider PROVIDER", String, "Update box with specific provider") do |p| options[:provider] = p.to_sym end + + build_download_options(o, download_options) end argv = parse_options(opts) return if !argv if options[:box] - update_specific(options[:box], options[:provider]) + update_specific(options[:box], options[:provider], download_options) else - update_vms(argv, options[:provider]) + update_vms(argv, options[:provider], download_options) end 0 end - def update_specific(name, provider) + def update_specific(name, provider, download_options) boxes = {} @env.boxes.all.each do |n, v, p| boxes[n] ||= {} @@ -74,11 +81,11 @@ module VagrantPlugins to_update.each do |n, p, v| box = @env.boxes.find(n, p, v) - box_update(box, "> #{v}", @env.ui) + box_update(box, "> #{v}", @env.ui, download_options) end end - def update_vms(argv, provider) + def update_vms(argv, provider, download_options) with_target_vms(argv, provider: provider) do |machine| if !machine.config.vm.box machine.ui.output(I18n.t( @@ -95,17 +102,25 @@ module VagrantPlugins box = machine.box version = machine.config.vm.box_version - box_update(box, version, machine.ui) + # Get download options from machine configuration if not specified + # on the command line. + download_options[:ca_cert] ||= machine.config.vm.box_download_ca_cert + download_options[:ca_path] ||= machine.config.vm.box_download_ca_path + download_options[:client_cert] ||= machine.config.vm.box_download_client_cert + if download_options[:insecure].nil? + download_options[:insecure] = machine.config.vm.box_download_insecure + end + box_update(box, version, machine.ui, download_options) end end - def box_update(box, version, ui) + def box_update(box, version, ui, download_options) ui.output(I18n.t("vagrant.box_update_checking", name: box.name)) ui.detail("Latest installed version: #{box.version}") ui.detail("Version constraints: #{version}") ui.detail("Provider: #{box.provider}") - update = box.has_update?(version) + update = box.has_update?(version, download_options: download_options) if !update ui.success(I18n.t( "vagrant.box_up_to_date_single", @@ -124,6 +139,10 @@ module VagrantPlugins box_provider: update[2].name, box_version: update[1].version, ui: ui, + box_client_cert: download_options[:client_cert], + box_download_ca_cert: download_options[:ca_cert], + box_download_ca_path: download_options[:ca_path], + box_download_insecure: download_options[:insecure] }) end end diff --git a/plugins/commands/login/client.rb b/plugins/commands/login/client.rb index 63b01f335..fe8811935 100644 --- a/plugins/commands/login/client.rb +++ b/plugins/commands/login/client.rb @@ -1,4 +1,5 @@ require "rest_client" +require "vagrant/util/downloader" module VagrantPlugins module LoginCommand @@ -45,8 +46,23 @@ module VagrantPlugins 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) + + proxy = nil + proxy ||= ENV["HTTPS_PROXY"] || ENV["https_proxy"] + proxy ||= ENV["HTTP_PROXY"] || ENV["http_proxy"] + + response = RestClient::Request.execute( + method: :post, + url: url, + payload: JSON.dump(request), + proxy: proxy, + headers: { + accept: :json, + content_type: :json, + user_agent: Vagrant::Util::Downloader::USER_AGENT, + }, + ) + data = JSON.load(response.to_s) data["token"] end diff --git a/plugins/commands/login/middleware/add_authentication.rb b/plugins/commands/login/middleware/add_authentication.rb index faaa3cfe7..f176e3480 100644 --- a/plugins/commands/login/middleware/add_authentication.rb +++ b/plugins/commands/login/middleware/add_authentication.rb @@ -1,3 +1,4 @@ +require "cgi" require "uri" require_relative "../client" @@ -5,6 +6,9 @@ require_relative "../client" module VagrantPlugins module LoginCommand class AddAuthentication + VCLOUD = "vagrantcloud.com".freeze + ATLAS = "atlas.hashicorp.com".freeze + def initialize(app, env) @app = app end @@ -19,19 +23,26 @@ module VagrantPlugins 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" + if u.host == VCLOUD && server_uri.host == ATLAS + replace = true + end end if replace - u.query ||= "" - u.query += "&" if u.query != "" - u.query += "access_token=#{token}" + q = CGI.parse(u.query || "") + + current = q["access_token"] + if current && current.empty? + q["access_token"] = token + end + + u.query = URI.encode_www_form(q) end u.to_s diff --git a/plugins/commands/plugin/command/mixin_install_opts.rb b/plugins/commands/plugin/command/mixin_install_opts.rb index 0b1b0973a..004fd10b0 100644 --- a/plugins/commands/plugin/command/mixin_install_opts.rb +++ b/plugins/commands/plugin/command/mixin_install_opts.rb @@ -3,6 +3,11 @@ module VagrantPlugins module Command module MixinInstallOpts def build_install_opts(o, options) + options[:plugin_sources] = [ + "https://rubygems.org", + "http://gems.hashicorp.com", + ] + o.on("--entry-point NAME", String, "The name of the entry point file for loading the plugin.") do |entry_point| options[:entry_point] = entry_point @@ -17,9 +22,13 @@ module VagrantPlugins puts end + o.on("--plugin-clean-sources", + "Remove all plugin sources defined so far (including defaults)") do |clean| + options[:plugin_sources] = [] if clean + end + o.on("--plugin-source PLUGIN_SOURCE", String, "Add a RubyGems repository source") do |plugin_source| - options[:plugin_sources] ||= [] options[:plugin_sources] << plugin_source end diff --git a/plugins/commands/port/command.rb b/plugins/commands/port/command.rb new file mode 100644 index 000000000..af35def4b --- /dev/null +++ b/plugins/commands/port/command.rb @@ -0,0 +1,90 @@ +require "vagrant/util/presence" + +require "optparse" + +module VagrantPlugins + module CommandPort + class Command < Vagrant.plugin("2", :command) + include Vagrant::Util::Presence + + def self.synopsis + "displays information about guest port mappings" + end + + def execute + options = {} + + opts = OptionParser.new do |o| + o.banner = "Usage: vagrant port [options] [name]" + o.separator "" + o.separator "Options:" + o.separator "" + + o.on("--guest PORT", "Output the host port that maps to the given guest port") do |port| + options[:guest] = port + end + + o.on("--machine-readable", "Display machine-readable output") + end + + # Parse the options + argv = parse_options(opts) + return if !argv + + with_target_vms(argv, single_target: true) do |vm| + vm.action_raw(:config_validate, + Vagrant::Action::Builtin::ConfigValidate) + + if !vm.provider.capability?(:forwarded_ports) + @env.ui.error(I18n.t("port_command.missing_capability", + provider: vm.provider_name, + )) + return 1 + end + + ports = vm.provider.capability(:forwarded_ports) + + if !present?(ports) + @env.ui.info(I18n.t("port_command.empty_ports")) + return 0 + end + + if present?(options[:guest]) + return print_single(vm, ports, options[:guest]) + else + return print_all(vm, ports) + end + end + end + + private + + # Print all the guest <=> host port mappings. + # @return [0] the exit code + def print_all(vm, ports) + @env.ui.info(I18n.t("port_command.details")) + @env.ui.info("") + ports.each do |host, guest| + @env.ui.info("#{guest.to_s.rjust(6)} (guest) => #{host} (host)") + @env.ui.machine("forwarded_port", guest, host, target: vm.name.to_s) + end + return 0 + end + + # Print the host mapping that matches the given guest target. + # @return [0,1] the exit code + def print_single(vm, ports, target) + map = ports.find { |_, guest| "#{guest}" == "#{target}" } + if !present?(map) + @env.ui.error(I18n.t("port_command.no_matching_port", + port: target, + )) + return 1 + end + + @env.ui.info("#{map[0]}") + return 0 + end + end + end +end diff --git a/plugins/commands/port/locales/en.yml b/plugins/commands/port/locales/en.yml new file mode 100644 index 000000000..ba1389db7 --- /dev/null +++ b/plugins/commands/port/locales/en.yml @@ -0,0 +1,20 @@ +en: + port_command: + details: |- + The forwarded ports for the machine are listed below. Please note that + these values may differ from values configured in the Vagrantfile if the + provider supports automatic port collision detection and resolution. + empty_ports: |- + The provider reported there are no forwarded ports for this virtual + machine. This can be caused if there are no ports specified in the + Vagrantfile or if the virtual machine is not currently running. Please + check that the virtual machine is running and try again. + missing_capability: |- + The %{provider} provider does not support listing forwarded ports. This is + most likely a limitation of the provider and not a bug in Vagrant. If you + believe this is a bug in Vagrant, please search existing issues before + opening a new one. + no_matching_port: |- + The guest is not currently mapping port %{port} to the host machine. Is + the port configured in the Vagrantfile? You may need to run `vagrant reload` + if changes were made to the port configuration in the Vagrantfile. diff --git a/plugins/commands/port/plugin.rb b/plugins/commands/port/plugin.rb new file mode 100644 index 000000000..7437f0436 --- /dev/null +++ b/plugins/commands/port/plugin.rb @@ -0,0 +1,27 @@ +require "vagrant" + +module VagrantPlugins + module CommandPort + class Plugin < Vagrant.plugin("2") + name "port command" + description <<-DESC + The `port` command displays guest port mappings. + DESC + + command("port") do + require_relative "command" + self.init! + Command + 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/powershell/command.rb b/plugins/commands/powershell/command.rb new file mode 100644 index 000000000..836c22a02 --- /dev/null +++ b/plugins/commands/powershell/command.rb @@ -0,0 +1,119 @@ +require "optparse" + +require "vagrant/util/powershell" +require_relative "../../communicators/winrm/helper" + +module VagrantPlugins + module CommandPS + class Command < Vagrant.plugin("2", :command) + def self.synopsis + "connects to machine via powershell remoting" + end + + def execute + options = {} + + opts = OptionParser.new do |o| + o.banner = "Usage: vagrant powershell [-- extra powershell args]" + + o.separator "" + o.separator "Options:" + o.separator "" + + o.on("-c", "--command COMMAND", "Execute a powershell command directly") do |c| + options[:command] = c + end + end + + # Parse out the extra args to send to the ps session, which + # is everything after the "--" + split_index = @argv.index("--") + if split_index + options[:extra_args] = @argv.drop(split_index + 1) + @argv = @argv.take(split_index) + end + + # Parse the options and return if we don't have any target. + argv = parse_options(opts) + return if !argv + + # Check if the host even supports ps remoting + raise Errors::HostUnsupported if !@env.host.capability?(:ps_client) + + # Execute ps session if we can + with_target_vms(argv, single_target: true) do |machine| + if !machine.communicate.ready? + raise Vagrant::Errors::VMNotCreatedError + end + + if machine.config.vm.communicator != :winrm + raise VagrantPlugins::CommunicatorWinRM::Errors::WinRMNotReady + end + + if !options[:command].nil? + out_code = machine.communicate.execute(options[:command].dup) do |type,data| + machine.ui.detail(data) if type == :stdout + end + if out_code == 0 + machine.ui.success("Command: #{options[:command]} executed succesfully with output code #{out_code}.") + end + next + end + + ps_info = VagrantPlugins::CommunicatorWinRM::Helper.winrm_info(machine) + ps_info[:username] = machine.config.winrm.username + ps_info[:password] = machine.config.winrm.password + # Extra arguments if we have any + ps_info[:extra_args] = options[:extra_args] + + result = ready_ps_remoting_for(machine, ps_info) + + machine.ui.detail( + "Creating powershell session to #{ps_info[:host]}:#{ps_info[:port]}") + machine.ui.detail("Username: #{ps_info[:username]}") + + begin + @env.host.capability(:ps_client, ps_info) + ensure + if !result["PreviousTrustedHosts"].nil? + reset_ps_remoting_for(machine, ps_info) + end + end + end + end + + def ready_ps_remoting_for(machine, ps_info) + machine.ui.output(I18n.t("vagrant_ps.detecting")) + script_path = File.expand_path("../scripts/enable_psremoting.ps1", __FILE__) + args = [] + args << "-hostname" << ps_info[:host] + args << "-port" << ps_info[:port].to_s + args << "-username" << ps_info[:username] + args << "-password" << ps_info[:password] + result = Vagrant::Util::PowerShell.execute(script_path, *args) + if result.exit_code != 0 + raise Errors::PowerShellError, + script: script_path, + stderr: result.stderr + end + + result_output = JSON.parse(result.stdout) + raise Errors::PSRemotingUndetected if !result_output["Success"] + result_output + end + + def reset_ps_remoting_for(machine, ps_info) + machine.ui.output(I18n.t("vagrant_ps.reseting")) + script_path = File.expand_path("../scripts/reset_trustedhosts.ps1", __FILE__) + args = [] + args << "-hostname" << ps_info[:host] + result = Vagrant::Util::PowerShell.execute(script_path, *args) + if result.exit_code != 0 + raise Errors::PowerShellError, + script: script_path, + stderr: result.stderr + end + end + end + end +end diff --git a/plugins/commands/powershell/errors.rb b/plugins/commands/powershell/errors.rb new file mode 100644 index 000000000..4be70551a --- /dev/null +++ b/plugins/commands/powershell/errors.rb @@ -0,0 +1,22 @@ +module VagrantPlugins + module CommandPS + module Errors + # A convenient superclass for all our errors. + class PSCommandError < Vagrant::Errors::VagrantError + error_namespace("vagrant_ps.errors") + end + + class HostUnsupported < PSCommandError + error_key(:host_unsupported) + end + + class PSRemotingUndetected < PSCommandError + error_key(:ps_remoting_undetected) + end + + class PowerShellError < PSCommandError + error_key(:powershell_error) + end + end + end +end diff --git a/plugins/commands/powershell/plugin.rb b/plugins/commands/powershell/plugin.rb new file mode 100644 index 000000000..325925115 --- /dev/null +++ b/plugins/commands/powershell/plugin.rb @@ -0,0 +1,30 @@ +require "vagrant" + +module VagrantPlugins + module CommandPS + autoload :Errors, File.expand_path("../errors", __FILE__) + + class Plugin < Vagrant.plugin("2") + name "powershell command" + description <<-DESC + The powershell command opens a remote PowerShell session to the + machine if it supports powershell remoting. + DESC + + command("powershell") do + require_relative "command" + init! + Command + end + + protected + + def self.init! + return if defined?(@_init) + I18n.load_path << File.expand_path("templates/locales/command_ps.yml", Vagrant.source_root) + I18n.reload! + @_init = true + end + end + end +end diff --git a/plugins/commands/powershell/scripts/enable_psremoting.ps1 b/plugins/commands/powershell/scripts/enable_psremoting.ps1 new file mode 100644 index 000000000..4d7ded704 --- /dev/null +++ b/plugins/commands/powershell/scripts/enable_psremoting.ps1 @@ -0,0 +1,60 @@ +Param( + [string]$hostname, + [string]$port, + [string]$username, + [string]$password +) +# If we are in this script, we know basic winrm is working +# If the user is not using a domain acount and chances are +# they are not, PS Remoting will not work if the guest is not +# listed in the trusted hosts. + +$encrypted_password = ConvertTo-SecureString $password -asplaintext -force +$creds = New-Object System.Management.Automation.PSCredential ( + "$hostname\\$username", $encrypted_password) + +$result = @{ + Success = $false + PreviousTrustedHosts = $null +} +try { + invoke-command -computername $hostname ` + -Credential $creds ` + -Port $port ` + -ScriptBlock {} ` + -ErrorAction Stop + $result.Success = $true +} catch{} + +if(!$result.Success) { + $newHosts = @() + $result.PreviousTrustedHosts=( + Get-Item "wsman:\localhost\client\trustedhosts").Value + $hostArray=$result.PreviousTrustedHosts.Split(",").Trim() + if($hostArray -contains "*") { + $result.PreviousTrustedHosts = $null + } + elseif(!($hostArray -contains $hostname)) { + $strNewHosts = $hostname + if($result.PreviousTrustedHosts.Length -gt 0){ + $strNewHosts = $result.PreviousTrustedHosts + "," + $strNewHosts + } + Set-Item -Path "wsman:\localhost\client\trustedhosts" ` + -Value $strNewHosts -Force + + try { + invoke-command -computername $hostname ` + -Credential $creds ` + -Port $port ` + -ScriptBlock {} ` + -ErrorAction Stop + $result.Success = $true + } catch{ + Set-Item -Path "wsman:\localhost\client\trustedhosts" ` + -Value $result.PreviousTrustedHosts -Force + $result.PreviousTrustedHosts = $null + } + } +} + +Write-Output $(ConvertTo-Json $result) diff --git a/plugins/commands/powershell/scripts/reset_trustedhosts.ps1 b/plugins/commands/powershell/scripts/reset_trustedhosts.ps1 new file mode 100644 index 000000000..5865a4e77 --- /dev/null +++ b/plugins/commands/powershell/scripts/reset_trustedhosts.ps1 @@ -0,0 +1,12 @@ +Param( + [string]$hostname +) + +$trustedHosts = ( + Get-Item "wsman:\localhost\client\trustedhosts").Value.Replace( + $hostname, '') +$trustedHosts = $trustedHosts.Replace(",,","") +if($trustedHosts.EndsWith(",")){ + $trustedHosts = $trustedHosts.Substring(0,$trustedHosts.length-1) +} +Set-Item "wsman:\localhost\client\trustedhosts" -Value $trustedHosts -Force \ No newline at end of file diff --git a/plugins/commands/provision/command.rb b/plugins/commands/provision/command.rb index a67084f75..0dc5bdf00 100644 --- a/plugins/commands/provision/command.rb +++ b/plugins/commands/provision/command.rb @@ -15,7 +15,7 @@ module VagrantPlugins o.banner = "Usage: vagrant provision [vm-name] [--provision-with x,y,z]" o.on("--provision-with x,y,z", Array, - "Enable only certain provisioners, by type.") do |list| + "Enable only certain provisioners, by type or by name.") do |list| options[:provision_types] = list.map { |type| type.to_sym } end end diff --git a/plugins/commands/reload/command.rb b/plugins/commands/reload/command.rb index 33a6f8e5e..1d43d13b7 100644 --- a/plugins/commands/reload/command.rb +++ b/plugins/commands/reload/command.rb @@ -30,7 +30,7 @@ module VagrantPlugins return if !argv # Validate the provisioners - validate_provisioner_flags!(options) + validate_provisioner_flags!(options, argv) @logger.debug("'reload' each target VM...") machines = [] diff --git a/plugins/commands/ssh_config/command.rb b/plugins/commands/ssh_config/command.rb index 6013a90b7..320c8ace5 100644 --- a/plugins/commands/ssh_config/command.rb +++ b/plugins/commands/ssh_config/command.rb @@ -41,12 +41,15 @@ module VagrantPlugins forward_agent: ssh_info[:forward_agent], forward_x11: ssh_info[:forward_x11], proxy_command: ssh_info[:proxy_command], - ssh_command: ssh_info[:ssh_command] + ssh_command: ssh_info[:ssh_command], + forward_env: ssh_info[:forward_env], } # Render the template and output directly to STDOUT template = "commands/ssh_config/config" - safe_puts(Vagrant::Util::TemplateRenderer.render(template, variables)) + config = Vagrant::Util::TemplateRenderer.render(template, variables) + machine.ui.machine("ssh-config", config) + safe_puts(config) safe_puts end diff --git a/plugins/commands/up/command.rb b/plugins/commands/up/command.rb index b93e859cf..7573a088a 100644 --- a/plugins/commands/up/command.rb +++ b/plugins/commands/up/command.rb @@ -55,7 +55,7 @@ module VagrantPlugins return if !argv # Validate the provisioners - validate_provisioner_flags!(options) + validate_provisioner_flags!(options, argv) # Go over each VM and bring it up @logger.debug("'Up' each target VM...") diff --git a/plugins/commands/up/start_mixins.rb b/plugins/commands/up/start_mixins.rb index 548d889ca..bb397346c 100644 --- a/plugins/commands/up/start_mixins.rb +++ b/plugins/commands/up/start_mixins.rb @@ -1,3 +1,5 @@ +require "set" + module VagrantPlugins module CommandUp module StartMixins @@ -17,7 +19,7 @@ module VagrantPlugins end parser.on("--provision-with x,y,z", Array, - "Enable only certain provisioners, by type.") do |list| + "Enable only certain provisioners, by type or by name.") do |list| options[:provision_types] = list.map { |type| type.to_sym } options[:provision_enabled] = true options[:provision_ignore_sentinel] = true @@ -26,13 +28,26 @@ module VagrantPlugins # This validates the provisioner flags and raises an exception # if there are invalid ones. - def validate_provisioner_flags!(options) - (options[:provision_types] || []).each do |type| - klass = Vagrant.plugin("2").manager.provisioners[type] - if !klass - raise Vagrant::Errors::ProvisionerFlagInvalid, - name: type.to_s - end + def validate_provisioner_flags!(options, argv) + if options[:provision_types].nil? + return + end + + provisioner_names = Set.new + with_target_vms(argv) do |machine| + machine.config.vm.provisioners.map(&:name).each do |name| + provisioner_names.add(name) + end + end + + if (provisioner_names & options[:provision_types]).empty? + (options[:provision_types] || []).each do |type| + klass = Vagrant.plugin("2").manager.provisioners[type] + if !klass + raise Vagrant::Errors::ProvisionerFlagInvalid, + name: type.to_s + end + end end end end diff --git a/plugins/communicators/ssh/communicator.rb b/plugins/communicators/ssh/communicator.rb index 8297329f9..a9190f569 100644 --- a/plugins/communicators/ssh/communicator.rb +++ b/plugins/communicators/ssh/communicator.rb @@ -333,6 +333,7 @@ module VagrantPlugins auth_methods: auth_methods, config: false, forward_agent: ssh_info[:forward_agent], + send_env: ssh_info[:forward_env], keys: ssh_info[:private_key_path], keys_only: true, paranoid: false, @@ -420,7 +421,7 @@ module VagrantPlugins rescue Errno::EHOSTDOWN # This is raised if we get an ICMP DestinationUnknown error. raise Vagrant::Errors::SSHHostDown - rescue Errno::EHOSTUNREACH + rescue Errno::EHOSTUNREACH, Errno::ENETUNREACH # This is raised if we can't work out how to route traffic. raise Vagrant::Errors::SSHNoRoute rescue Net::SSH::Exception => e @@ -611,6 +612,7 @@ module VagrantPlugins end data = pty_stdout[/.*#{PTY_DELIM_START}(.*?)#{PTY_DELIM_END}/m, 1] + data ||= "" @logger.debug("PTY stdout parsed: #{data}") yield :stdout, data if block_given? end diff --git a/plugins/communicators/winrm/communicator.rb b/plugins/communicators/winrm/communicator.rb index b14b28efa..e22a4f9b0 100644 --- a/plugins/communicators/winrm/communicator.rb +++ b/plugins/communicators/winrm/communicator.rb @@ -43,6 +43,7 @@ module VagrantPlugins # 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 execution_time_limit: #{shell.execution_time_limit}") @machine.ui.detail("WinRM transport: #{shell.config.transport}") last_message = nil @@ -136,10 +137,11 @@ module VagrantPlugins error_key: nil, # use the error_class message key good_exit: 0, shell: :powershell, + interactive: false, }.merge(opts || {}) opts[:good_exit] = Array(opts[:good_exit]) - command = wrap_in_scheduled_task(command) if opts[:elevated] + command = wrap_in_scheduled_task(command, opts[:interactive]) if opts[:elevated] output = shell.send(opts[:shell], command, &block) execution_output(output, opts) end @@ -193,9 +195,11 @@ module VagrantPlugins # in place. # # @return The wrapper command to execute - def wrap_in_scheduled_task(command) + def wrap_in_scheduled_task(command, interactive) path = File.expand_path("../scripts/elevated_shell.ps1", __FILE__) - script = Vagrant::Util::TemplateRenderer.render(path) + script = Vagrant::Util::TemplateRenderer.render(path, options: { + interactive: interactive, + }) guest_script_path = "c:/tmp/vagrant-elevated-shell.ps1" file = Tempfile.new(["vagrant-elevated-shell", "ps1"]) begin @@ -208,14 +212,16 @@ module VagrantPlugins file.unlink end - # convert to double byte unicode string then base64 encode - # just like PowerShell -EncodedCommand expects + # Convert to double byte unicode string then base64 encode + # just like PowerShell -EncodedCommand expects. + # Suppress the progress stream from leaking to stderr. wrapped_encoded_command = Base64.strict_encode64( - "#{command}; exit $LASTEXITCODE".encode('UTF-16LE', 'UTF-8')) + "$ProgressPreference='SilentlyContinue'; #{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}\"" + "powershell -executionpolicy bypass -file '#{guest_script_path}' " + + "-username '#{shell.username}' -password '#{shell.password}' " + + "-encoded_command '#{wrapped_encoded_command}' " + + "-execution_time_limit '#{shell.execution_time_limit}'" end # Handles the raw WinRM shell result and converts it to a diff --git a/plugins/communicators/winrm/config.rb b/plugins/communicators/winrm/config.rb index 79901d503..3387ca142 100644 --- a/plugins/communicators/winrm/config.rb +++ b/plugins/communicators/winrm/config.rb @@ -11,6 +11,7 @@ module VagrantPlugins attr_accessor :timeout attr_accessor :transport attr_accessor :ssl_peer_verification + attr_accessor :execution_time_limit def initialize @username = UNSET_VALUE @@ -23,6 +24,7 @@ module VagrantPlugins @timeout = UNSET_VALUE @transport = UNSET_VALUE @ssl_peer_verification = UNSET_VALUE + @execution_time_limit = UNSET_VALUE end def finalize! @@ -37,6 +39,7 @@ module VagrantPlugins @retry_delay = 2 if @retry_delay == UNSET_VALUE @timeout = 1800 if @timeout == UNSET_VALUE @ssl_peer_verification = true if @ssl_peer_verification == UNSET_VALUE + @execution_time_limit = "PT2H" if @execution_time_limit == UNSET_VALUE end def validate(machine) @@ -49,6 +52,7 @@ module VagrantPlugins 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? + errors << "winrm.execution_time_limit cannot be nil." if @execution_time_limit.nil? unless @ssl_peer_verification == true || @ssl_peer_verification == false errors << "winrm.ssl_peer_verification must be a boolean." end diff --git a/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb b/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb index 17767e436..76b260b78 100644 --- a/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb +++ b/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb @@ -1,5 +1,17 @@ -param([String]$username, [String]$password, [String]$encoded_command) +param([String]$username, [String]$password, [String]$encoded_command, [String]$execution_time_limit) +# Try to get the Schedule.Service object. If it fails, we are probably +# on an older version of Windows. On old versions, we can just execute +# directly since priv. escalation isn't a thing. +$schedule = $null +Try { + $schedule = New-Object -ComObject "Schedule.Service" +} Catch [System.Management.Automation.PSArgumentException] { + powershell.exe -EncodedCommand $encoded_command + exit $LASTEXITCODE +} + +$ProgressPreference = "SilentlyContinue" $task_name = "WinRM_Elevated_Shell" $out_file = "$env:SystemRoot\Temp\WinRM_Elevated_Shell.log" @@ -13,7 +25,7 @@ $task_xml = @' {username} - Password + <%= options[:interactive] ? 'InteractiveTokenOrPassword' : 'Password' %> HighestAvailable @@ -33,7 +45,7 @@ $task_xml = @' false false false - PT2H + {execution_time_limit} 4 @@ -49,13 +61,13 @@ $arguments = "/c powershell.exe -EncodedCommand $encoded_command > $out_file $task_xml = $task_xml.Replace("{arguments}", $arguments) $task_xml = $task_xml.Replace("{username}", $username) +$task_xml = $task_xml.Replace("{execution_time_limit}", $execution_time_limit) -$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, $username, $password, 1, $null) | Out-Null +$folder.RegisterTaskDefinition($task_name, $task, 6, $username, $password, <%= options[:interactive] ? 3 : 1 %>, $null) | Out-Null $registered_task = $folder.GetTask("\$task_name") $registered_task.Run($null) | Out-Null @@ -71,7 +83,7 @@ function SlurpOutput($out_file, $cur_line) { if (Test-Path $out_file) { get-content $out_file | select -skip $cur_line | ForEach { $cur_line += 1 - Write-Host "$_" + Write-Host "$_" } } return $cur_line diff --git a/plugins/communicators/winrm/shell.rb b/plugins/communicators/winrm/shell.rb index 9b0f4e302..f8a50a3a7 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 "winrm-fs/file_manager" +require "winrm-fs" module VagrantPlugins module CommunicatorWinRM @@ -23,6 +23,7 @@ module VagrantPlugins HTTPClient::KeepAliveDisconnected, WinRM::WinRMHTTPTransportError, WinRM::WinRMAuthorizationError, + WinRM::WinRMWSManFault, Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED, @@ -37,6 +38,7 @@ module VagrantPlugins attr_reader :port attr_reader :username attr_reader :password + attr_reader :execution_time_limit attr_reader :config def initialize(host, port, config) @@ -47,12 +49,15 @@ module VagrantPlugins @port = port @username = config.username @password = config.password + @execution_time_limit = config.execution_time_limit @config = config end def powershell(command, &block) - # ensure an exit code + # Suppress the progress stream from leaking to stderr + command = "$ProgressPreference='SilentlyContinue';\r\n" + command command << "\r\n" + # Ensure an exit code command << "if ($?) { exit 0 } else { if($LASTEXITCODE) { exit $LASTEXITCODE } else { exit 1 } }" execute_shell(command, :powershell, &block) end diff --git a/plugins/guests/arch/cap/configure_networks.rb b/plugins/guests/arch/cap/configure_networks.rb index fd06c24ea..1b95469fc 100644 --- a/plugins/guests/arch/cap/configure_networks.rb +++ b/plugins/guests/arch/cap/configure_networks.rb @@ -27,7 +27,7 @@ module VagrantPlugins machine.communicate.upload(temp.path, "/tmp/vagrant_network") machine.communicate.sudo("mv /tmp/vagrant_network /etc/netctl/#{network[:device]}") - machine.communicate.sudo("ip link set #{network[:device]} down && netctl start #{network[:device]}") + machine.communicate.sudo("ip link set #{network[:device]} down && netctl start #{network[:device]} && netctl enable #{network[:device]}") end end end diff --git a/plugins/guests/darwin/cap/configure_networks.rb b/plugins/guests/darwin/cap/configure_networks.rb index fefd0a2e2..1be71c82f 100644 --- a/plugins/guests/darwin/cap/configure_networks.rb +++ b/plugins/guests/darwin/cap/configure_networks.rb @@ -6,45 +6,109 @@ module VagrantPlugins module GuestDarwin module Cap class ConfigureNetworks + @@logger = Log4r::Logger.new("vagrant::guest::darwin::configure_networks") + include Vagrant::Util def self.configure_networks(machine, networks) - # Slightly different than other plugins, using the template to build commands - # rather than templating the files. + if !machine.provider.capability?(:nic_mac_addresses) + raise Vagrant::Errors::CantReadMACAddresses, + provider: machine.provider_name.to_s + end - machine.communicate.sudo("networksetup -detectnewhardware") - machine.communicate.sudo("networksetup -listnetworkserviceorder > /tmp/vagrant.interfaces") - tmpints = File.join(Dir.tmpdir, File.basename("#{machine.id}.interfaces")) - machine.communicate.download("/tmp/vagrant.interfaces",tmpints) + nic_mac_addresses = machine.provider.capability(:nic_mac_addresses) + @@logger.debug("mac addresses: #{nic_mac_addresses.inspect}") - devlist = [] - ints = ::IO.read(tmpints) + mac_service_map = create_mac_service_map(machine) + + networks.each do |network| + mac_address = nic_mac_addresses[network[:interface]+1] + if mac_address.nil? + @@logger.warn("Could not find mac address for network #{network.inspect}") + next + end + + service_name = mac_service_map[mac_address] + if service_name.nil? + @@logger.warn("Could not find network service for mac address #{mac_address}") + next + end + + network_type = network[:type].to_sym + if network_type == :static + command = "networksetup -setmanual \"#{service_name}\" #{network[:ip]} #{network[:netmask]}" + elsif network_type == :dhcp + command = "networksetup -setdhcp \"#{service_name}\"" + else + raise "#{network_type} network type is not supported, try static or dhcp" + end + + machine.communicate.sudo(command) + end + end + + # Creates a hash mapping MAC addresses to network service name + # Example: { "00C100A1B2C3" => "Thunderbolt Ethernet" } + def self.create_mac_service_map(machine) + tmp_ints = File.join(Dir.tmpdir, File.basename("#{machine.id}.interfaces")) + tmp_hw = File.join(Dir.tmpdir, File.basename("#{machine.id}.hardware")) + + machine.communicate.tap do |comm| + comm.sudo("networksetup -detectnewhardware") + comm.sudo("networksetup -listnetworkserviceorder > /tmp/vagrant.interfaces") + comm.sudo("networksetup -listallhardwareports > /tmp/vagrant.hardware") + comm.download("/tmp/vagrant.interfaces", tmp_ints) + comm.download("/tmp/vagrant.hardware", tmp_hw) + end + + interface_map = {} + ints = ::IO.read(tmp_ints) ints.split(/\n\n/m).each do |i| - if i.match(/Hardware/) and not i.match(/Ethernet/).nil? - devmap = {} + if i.match(/Hardware/) && i.match(/Ethernet/) # Ethernet, should be 2 lines, # (3) Thunderbolt Ethernet # (Hardware Port: Thunderbolt Ethernet, Device: en1) # multiline, should match "Thunderbolt Ethernet", "en1" devicearry = i.match(/\([0-9]+\) (.+)\n.*Device: (.+)\)/m) - devmap[:interface] = devicearry[2] - devmap[:service] = devicearry[1] - devlist << devmap + service = devicearry[1] + interface = devicearry[2] + + # Should map interface to service { "en1" => "Thunderbolt Ethernet" } + interface_map[interface] = service end end - File.delete(tmpints) + File.delete(tmp_ints) - networks.each do |network| - service_name = devlist[network[:interface]][:service] - if network[:type].to_sym == :static - command = "networksetup -setmanual \"#{service_name}\" #{network[:ip]} #{network[:netmask]}" - elsif network[:type].to_sym == :dhcp - command = "networksetup -setdhcp \"#{service_name}\"" + mac_service_map = {} + macs = ::IO.read(tmp_hw) + macs.split(/\n\n/m).each do |i| + if i.match(/Hardware/) && i.match(/Ethernet/) + # Ethernet, should be 3 lines, + # Hardware Port: Thunderbolt 1 + # Device: en1 + # Ethernet Address: a1:b2:c3:d4:e5:f6 + + # multiline, should match "en1", "00:c1:00:a1:b2:c3" + devicearry = i.match(/Device: (.+)\nEthernet Address: (.+)/m) + interface = devicearry[1] + naked_mac = devicearry[2].gsub(':','').upcase + + # Skip hardware ports without MAC (bridges, bluetooth, etc.) + next if naked_mac == "N/A" + + if !interface_map[interface] + @@logger.warn("Could not find network service for interface #{interface}") + next + end + + # Should map MAC to service, { "00C100A1B2C3" => "Thunderbolt Ethernet" } + mac_service_map[naked_mac] = interface_map[interface] end - - machine.communicate.sudo(command) end + File.delete(tmp_hw) + + mac_service_map end end end diff --git a/plugins/guests/debian/cap/smb.rb b/plugins/guests/debian/cap/smb.rb new file mode 100644 index 000000000..fcc97af36 --- /dev/null +++ b/plugins/guests/debian/cap/smb.rb @@ -0,0 +1,18 @@ +module VagrantPlugins + module GuestDebian + module Cap + class SMB + def self.smb_install(machine) + # Deb/Ubuntu require mount.cifs which doesn't come by default. + machine.communicate.tap do |comm| + if !comm.test("test -f /sbin/mount.cifs") + machine.ui.detail(I18n.t("vagrant.guest_deb_installing_smb")) + comm.sudo("apt-get -y update") + comm.sudo("apt-get -y install cifs-utils") + end + end + end + end + end + end +end diff --git a/plugins/guests/debian/guest.rb b/plugins/guests/debian/guest.rb index be08b3605..a7f3d65de 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' | grep -v '8'") + machine.communicate.test("cat /etc/issue | grep 'Debian'") end end end diff --git a/plugins/guests/debian/plugin.rb b/plugins/guests/debian/plugin.rb index 0ba72ed5d..5d00583e8 100644 --- a/plugins/guests/debian/plugin.rb +++ b/plugins/guests/debian/plugin.rb @@ -30,6 +30,11 @@ module VagrantPlugins require_relative "cap/rsync" Cap::RSync end + + guest_capability("debian", "smb_install") do + require_relative "cap/smb" + Cap::SMB + end end end end diff --git a/plugins/guests/debian8/cap/halt.rb b/plugins/guests/debian8/cap/halt.rb deleted file mode 100644 index 932281347..000000000 --- a/plugins/guests/debian8/cap/halt.rb +++ /dev/null @@ -1,16 +0,0 @@ -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/plugin.rb b/plugins/guests/debian8/plugin.rb deleted file mode 100644 index 1f566f5fa..000000000 --- a/plugins/guests/debian8/plugin.rb +++ /dev/null @@ -1,21 +0,0 @@ -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/change_host_name.rb b/plugins/guests/fedora/cap/change_host_name.rb index 2a93b9de6..f0c95eec6 100644 --- a/plugins/guests/fedora/cap/change_host_name.rb +++ b/plugins/guests/fedora/cap/change_host_name.rb @@ -48,7 +48,7 @@ module VagrantPlugins def update_etc_hosts ip_address = '([0-9]{1,3}\.){3}[0-9]{1,3}' search = "^(#{ip_address})\\s+#{Regexp.escape(current_hostname)}(\\s.*)?$" - replace = "\\1 #{fqdn} #{short_hostname}" + replace = "\\1 #{fqdn} #{short_hostname} \\3" expression = ['s', search, replace, 'g'].join('@') sudo("sed -ri '#{expression}' /etc/hosts") @@ -72,4 +72,4 @@ module VagrantPlugins end end end -end \ No newline at end of file +end diff --git a/plugins/guests/fedora/cap/configure_networks.rb b/plugins/guests/fedora/cap/configure_networks.rb index 364b744e4..b95d0bdb7 100644 --- a/plugins/guests/fedora/cap/configure_networks.rb +++ b/plugins/guests/fedora/cap/configure_networks.rb @@ -109,13 +109,23 @@ module VagrantPlugins # SSH never dies. 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 + machine.communicate.sudo(<<-SCRIPT, error_check: true) +cat /tmp/vagrant-network-entry_#{interface} >> #{network_scripts_dir}/ifcfg-#{interface} - machine.communicate.sudo("rm -f /tmp/vagrant-network-entry_#{interface}") +if command -v nmcli &>/dev/null; then + if command -v systemctl &>/dev/null && systemctl -q is-enabled NetworkManager &>/dev/null; then + nmcli c reload #{interface} + elif command -v service &>/dev/null && service NetworkManager status &>/dev/null; then + nmcli c reload #{interface} + fi +fi + +/sbin/ifdown #{interface} +/sbin/ifup #{interface} + +rm -f /tmp/vagrant-network-entry_#{interface} +SCRIPT + end end end end diff --git a/plugins/guests/fedora/guest.rb b/plugins/guests/fedora/guest.rb index c1b48d4ab..f05861cf8 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 1[6789]\\|Fedora release 2[0-9]' /etc/redhat-release") + machine.communicate.test("grep 'Fedora release' /etc/redhat-release") end end end diff --git a/plugins/guests/linux/cap/mount_nfs.rb b/plugins/guests/linux/cap/mount_nfs.rb index ecc3ea9ee..26dfb625d 100644 --- a/plugins/guests/linux/cap/mount_nfs.rb +++ b/plugins/guests/linux/cap/mount_nfs.rb @@ -33,10 +33,11 @@ module VagrantPlugins end # Emit an upstart event if we can - 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 + machine.communicate.sudo <<-SCRIPT +if command -v /sbin/init &>/dev/null && /sbin/init --version | grep upstart &>/dev/null; then + /sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT='#{expanded_guest_path}' +fi +SCRIPT end end end diff --git a/plugins/guests/linux/cap/mount_smb_shared_folder.rb b/plugins/guests/linux/cap/mount_smb_shared_folder.rb index 136ccfb8a..9db7c0ef3 100644 --- a/plugins/guests/linux/cap/mount_smb_shared_folder.rb +++ b/plugins/guests/linux/cap/mount_smb_shared_folder.rb @@ -24,17 +24,14 @@ module VagrantPlugins mount_gid = "`getent group #{options[:group]} | cut -d: -f3`" mount_gid_old = "`id -g #{options[:group]}`" end - - smb_password = Shellwords.shellescape(options[:smb_password]) - + # If a domain is provided in the username, separate it username, domain = (options[:smb_username] || '').split('@', 2) + smb_password = options[:smb_password] options[:mount_options] ||= [] options[:mount_options] << "sec=ntlm" - options[:mount_options] << "username=#{username}" - options[:mount_options] << "password=#{smb_password}" - options[:mount_options] << "domain=#{domain}" if domain + options[:mount_options] << "credentials=/etc/smb_creds_#{name}" # First mount command uses getent to get the group mount_options = "-o uid=#{mount_uid},gid=#{mount_gid}" @@ -49,6 +46,16 @@ module VagrantPlugins # Create the guest path if it doesn't exist machine.communicate.sudo("mkdir -p #{expanded_guest_path}") + # Write the credentials file + machine.communicate.sudo(<<-SCRIPT) +cat </etc/smb_creds_#{name} +username=#{username} +password=#{smb_password} +#{domain ? "domain=#{domain}" : ""} +EOF +chmod 0600 /etc/smb_creds_#{name} +SCRIPT + # Attempt to mount the folder. We retry here a few times because # it can fail early on. attempts = 0 @@ -86,10 +93,11 @@ module VagrantPlugins end # Emit an upstart event if we can - 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 + machine.communicate.sudo <<-SCRIPT +if command -v /sbin/init &>/dev/null && /sbin/init --version | grep upstart &>/dev/null; then + /sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT='#{expanded_guest_path}' +fi +SCRIPT end end end diff --git a/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb b/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb index 56c864b6b..57fdc207f 100644 --- a/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb +++ b/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb @@ -80,10 +80,11 @@ module VagrantPlugins end # Emit an upstart event if we can - 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 + machine.communicate.sudo <<-SCRIPT +if command -v /sbin/init &>/dev/null && /sbin/init --version | grep upstart &>/dev/null; then + /sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT='#{expanded_guest_path}' +fi +SCRIPT end def self.unmount_virtualbox_shared_folder(machine, guestpath, options) diff --git a/plugins/guests/linux/cap/remove_public_key.rb b/plugins/guests/linux/cap/remove_public_key.rb index a8d773a40..dfd9daae5 100644 --- a/plugins/guests/linux/cap/remove_public_key.rb +++ b/plugins/guests/linux/cap/remove_public_key.rb @@ -10,8 +10,11 @@ module VagrantPlugins machine.communicate.tap do |comm| if comm.test("test -f ~/.ssh/authorized_keys") - comm.execute( - "sed -i '/^.*#{contents}.*$/d' ~/.ssh/authorized_keys") + comm.execute(< ~/.ssh/authorized_keys.new +mv ~/.ssh/authorized_keys.new ~/.ssh/authorized_keys +chmod 600 ~/.ssh/authorized_keys +SCRIPT end end end diff --git a/plugins/guests/pld/cap/flavor.rb b/plugins/guests/pld/cap/flavor.rb new file mode 100644 index 000000000..5ade9d18c --- /dev/null +++ b/plugins/guests/pld/cap/flavor.rb @@ -0,0 +1,11 @@ +module VagrantPlugins + module GuestPld + module Cap + class Flavor + def self.flavor(machine) + return :pld + end + end + end + end +end diff --git a/plugins/guests/pld/plugin.rb b/plugins/guests/pld/plugin.rb index ef7939fad..697599c81 100644 --- a/plugins/guests/pld/plugin.rb +++ b/plugins/guests/pld/plugin.rb @@ -20,6 +20,11 @@ module VagrantPlugins require_relative "cap/network_scripts_dir" Cap::NetworkScriptsDir end + + guest_capability("pld", "flavor") do + require_relative "cap/flavor" + Cap::Flavor + end end end end diff --git a/plugins/guests/redhat/cap/nfs_client.rb b/plugins/guests/redhat/cap/nfs_client.rb index ef88e9900..78e2d5bca 100644 --- a/plugins/guests/redhat/cap/nfs_client.rb +++ b/plugins/guests/redhat/cap/nfs_client.rb @@ -3,7 +3,11 @@ module VagrantPlugins module Cap class NFSClient def self.nfs_client_install(machine) - machine.communicate.sudo("yum -y install nfs-utils nfs-utils-lib") + if VagrantPlugins::GuestRedHat::Plugin.dnf?(machine) + machine.communicate.sudo("dnf -y install nfs-utils nfs-utils-lib") + else + machine.communicate.sudo("yum -y install nfs-utils nfs-utils-lib") + end restart_nfs(machine) end diff --git a/plugins/guests/redhat/cap/rsync.rb b/plugins/guests/redhat/cap/rsync.rb index 9a1cfeeac..8eace63f0 100644 --- a/plugins/guests/redhat/cap/rsync.rb +++ b/plugins/guests/redhat/cap/rsync.rb @@ -4,7 +4,11 @@ module VagrantPlugins class RSync def self.rsync_install(machine) machine.communicate.tap do |comm| - comm.sudo("yum -y install rsync") + if VagrantPlugins::GuestRedHat::Plugin.dnf?(machine) + comm.sudo("dnf -y install rsync") + else + comm.sudo("yum -y install rsync") + end end end end diff --git a/plugins/guests/redhat/plugin.rb b/plugins/guests/redhat/plugin.rb index 96e641bcb..0a97febee 100644 --- a/plugins/guests/redhat/plugin.rb +++ b/plugins/guests/redhat/plugin.rb @@ -45,6 +45,10 @@ module VagrantPlugins require_relative "cap/rsync" Cap::RSync end + + def self.dnf?(machine) + machine.communicate.test("/usr/bin/which -s dnf") + end end end end diff --git a/plugins/guests/slackware/cap/change_host_name.rb b/plugins/guests/slackware/cap/change_host_name.rb new file mode 100644 index 000000000..07976725f --- /dev/null +++ b/plugins/guests/slackware/cap/change_host_name.rb @@ -0,0 +1,19 @@ +module VagrantPlugins + module GuestSlackware + module Cap + class ChangeHostName + def self.change_host_name(machine, name) + machine.communicate.tap do |comm| + # Only do this if the hostname is not already set + if !comm.test("sudo hostname | grep '#{name}'") + comm.sudo("chmod o+w /etc/hostname") + comm.sudo("echo #{name} > /etc/hostname") + comm.sudo("chmod o-w /etc/hostname") + comm.sudo("hostname -F /etc/hostname") + end + end + end + end + end + end +end diff --git a/plugins/guests/slackware/cap/configure_networks.rb b/plugins/guests/slackware/cap/configure_networks.rb new file mode 100644 index 000000000..dc84080e0 --- /dev/null +++ b/plugins/guests/slackware/cap/configure_networks.rb @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +require "tempfile" + +require "vagrant/util/template_renderer" + +module VagrantPlugins + module GuestSlackware + module Cap + class ConfigureNetworks + 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| + network[:device] = interfaces[network[:interface]] + + entry = TemplateRenderer.render("guests/slackware/network_#{network[:type]}", options: network) + + temp = Tempfile.new("vagrant") + temp.binmode + temp.write(entry) + temp.close + + machine.communicate.upload(temp.path, "/tmp/vagrant_network") + machine.communicate.sudo("mv /tmp/vagrant_network /etc/rc.d/rc.inet1.conf") + machine.communicate.sudo("/etc/rc.d/rc.inet1") + end + end + end + end + end +end diff --git a/plugins/guests/debian8/guest.rb b/plugins/guests/slackware/guest.rb similarity index 53% rename from plugins/guests/debian8/guest.rb rename to plugins/guests/slackware/guest.rb index ec95e9e85..1442f26fc 100644 --- a/plugins/guests/debian8/guest.rb +++ b/plugins/guests/slackware/guest.rb @@ -1,8 +1,10 @@ +require "vagrant" + module VagrantPlugins - module GuestDebian8 + module GuestSlackware class Guest < Vagrant.plugin("2", :guest) def detect?(machine) - machine.communicate.test("cat /etc/issue | grep 'Debian' | grep '8'") + machine.communicate.test("cat /etc/slackware-version") end end end diff --git a/plugins/guests/slackware/plugin.rb b/plugins/guests/slackware/plugin.rb new file mode 100644 index 000000000..c81db6aa4 --- /dev/null +++ b/plugins/guests/slackware/plugin.rb @@ -0,0 +1,25 @@ +require "vagrant" + +module VagrantPlugins + module GuestSlackware + class Plugin < Vagrant.plugin("2") + name "Slackware guest" + description "Slackware guest support." + + guest("slackware", "linux") do + require File.expand_path("../guest", __FILE__) + Guest + end + + guest_capability("slackware", "change_host_name") do + require_relative "cap/change_host_name" + Cap::ChangeHostName + end + + guest_capability("slackware", "configure_networks") do + require_relative "cap/configure_networks" + Cap::ConfigureNetworks + end + end + end +end diff --git a/plugins/guests/ubuntu/cap/change_host_name.rb b/plugins/guests/ubuntu/cap/change_host_name.rb index 706d4850a..df68a010a 100644 --- a/plugins/guests/ubuntu/cap/change_host_name.rb +++ b/plugins/guests/ubuntu/cap/change_host_name.rb @@ -7,7 +7,7 @@ module VagrantPlugins end def update_etc_hostname - return super unless vivid? + return super unless systemd? sudo("hostnamectl set-hostname '#{short_hostname}'") end @@ -15,7 +15,7 @@ module VagrantPlugins if hardy? # hostname.sh returns 1, so use `true` to get a 0 exitcode sudo("/etc/init.d/hostname.sh start; true") - elsif vivid? + elsif systemd? # Service runs via hostnamectl else sudo("service hostname start") @@ -26,19 +26,25 @@ module VagrantPlugins 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 init_package + machine.communicate.execute('cat /proc/1/comm') do |type, data| + return data.chomp if type == :stdout + end + end + def os_version(name) machine.communicate.test("[ `lsb_release -c -s` = #{name} ]") end + + def systemd? + init_package == 'systemd' + end end end end diff --git a/plugins/guests/windows/cap/configure_networks.rb b/plugins/guests/windows/cap/configure_networks.rb index 8e766a319..a4f4685d9 100644 --- a/plugins/guests/windows/cap/configure_networks.rb +++ b/plugins/guests/windows/cap/configure_networks.rb @@ -53,7 +53,7 @@ module VagrantPlugins def self.create_vm_interface_map(machine, guest_network) if !machine.provider.capability?(:nic_mac_addresses) - raise Errors::CantReadMACAddresses, + raise Vagrant::Errors::CantReadMACAddresses, provider: machine.provider_name.to_s end diff --git a/plugins/guests/windows/cap/rsync.rb b/plugins/guests/windows/cap/rsync.rb index e391b92db..aa9f1bcdf 100644 --- a/plugins/guests/windows/cap/rsync.rb +++ b/plugins/guests/windows/cap/rsync.rb @@ -2,8 +2,17 @@ module VagrantPlugins module GuestWindows module Cap class RSync + def self.rsync_scrub_guestpath( machine, opts ) + # Windows guests most often use cygwin-dependent rsync utilities + # that expect "/cygdrive/c" instead of "c:" as the path prefix + # some vagrant code may pass guest paths with drive-lettered paths here + opts[:guestpath].gsub( /^([a-zA-Z]):/, '/cygdrive/\1' ) + end + def self.rsync_pre(machine, opts) machine.communicate.tap do |comm| + # rsync does not construct any gaps in the path to the target directory + # make sure that all subdirectories are created comm.execute("mkdir '#{opts[:guestpath]}'") end end diff --git a/plugins/guests/windows/errors.rb b/plugins/guests/windows/errors.rb index d5be14ce6..e76753646 100644 --- a/plugins/guests/windows/errors.rb +++ b/plugins/guests/windows/errors.rb @@ -6,10 +6,6 @@ module VagrantPlugins error_namespace("vagrant_windows.errors") end - class CantReadMACAddresses < WindowsError - error_key(:cant_read_mac_addresses) - end - class NetworkWinRMRequired < WindowsError error_key(:network_winrm_required) end diff --git a/plugins/guests/windows/plugin.rb b/plugins/guests/windows/plugin.rb index 47ca67e3f..d22dc2743 100644 --- a/plugins/guests/windows/plugin.rb +++ b/plugins/guests/windows/plugin.rb @@ -64,6 +64,11 @@ module VagrantPlugins Cap::MountSharedFolder end + guest_capability(:windows, :rsync_scrub_guestpath) do + require_relative "cap/rsync" + Cap::RSync + end + guest_capability(:windows, :rsync_pre) do require_relative "cap/rsync" Cap::RSync diff --git a/plugins/hosts/darwin/cap/provider_install_virtualbox.rb b/plugins/hosts/darwin/cap/provider_install_virtualbox.rb index c4748eecb..34f81ef3c 100644 --- a/plugins/hosts/darwin/cap/provider_install_virtualbox.rb +++ b/plugins/hosts/darwin/cap/provider_install_virtualbox.rb @@ -2,6 +2,7 @@ require "pathname" require "tempfile" require "vagrant/util/downloader" +require "vagrant/util/file_checksum" require "vagrant/util/subprocess" module VagrantPlugins @@ -10,8 +11,9 @@ module VagrantPlugins class ProviderInstallVirtualBox # The URL to download VirtualBox is hardcoded so we can have a # known-good version to download. - URL = "http://download.virtualbox.org/virtualbox/5.0.8/VirtualBox-5.0.8-103449-OSX.dmg".freeze - VERSION = "5.0.8".freeze + URL = "http://download.virtualbox.org/virtualbox/5.0.10/VirtualBox-5.0.10-104061-OSX.dmg".freeze + VERSION = "5.0.10".freeze + SHA256SUM = "62f933115498e51ddf5f2dab47dc1eebb42eb78ea1a7665cb91c53edacc847c6".freeze def self.provider_install_virtualbox(env) tf = Tempfile.new("vagrant") @@ -29,6 +31,15 @@ module VagrantPlugins dl = Vagrant::Util::Downloader.new(URL, tf.path, ui: ui) dl.download! + # Validate that the file checksum matches + actual = Vagrant::Util::FileChecksum.new(tf.path, Digest::SHA2).checksum + if actual != SHA256SUM + raise Vagrant::Errors::ProviderChecksumMismatch, + provider: "virtualbox", + actual: actual, + expected: SHA256SUM + end + # Launch it ui.output(I18n.t( "vagrant.hosts.darwin.virtualbox_install_install")) diff --git a/plugins/hosts/linux/cap/rdp.rb b/plugins/hosts/linux/cap/rdp.rb index 7146ef890..76476fc9b 100644 --- a/plugins/hosts/linux/cap/rdp.rb +++ b/plugins/hosts/linux/cap/rdp.rb @@ -5,17 +5,35 @@ module VagrantPlugins module Cap class RDP def self.rdp_client(env, rdp_info) - if !Vagrant::Util::Which.which("rdesktop") - raise Vagrant::Errors::LinuxRDesktopNotFound - end + # Detect if an RDP client is available. + # Prefer xfreerdp as it supports newer versions of RDP. + rdp_client = + if Vagrant::Util::Which.which("xfreerdp") + "xfreerdp" + elsif Vagrant::Util::Which.which("rdesktop") + "rdesktop" + else + raise Vagrant::Errors::LinuxRDPClientNotFound + end args = [] - args << "-u" << rdp_info[:username] - args << "-p" << rdp_info[:password] if rdp_info[:password] - args += rdp_info[:extra_args] if rdp_info[:extra_args] - args << "#{rdp_info[:host]}:#{rdp_info[:port]}" - Vagrant::Util::Subprocess.execute("rdesktop", *args) + # Build appropriate arguments for the RDP client. + case rdp_client + when "xfreerdp" + args << "/u:#{rdp_info[:username]}" + args << "/p:#{rdp_info[:password]}" if rdp_info[:password] + args << "/v:#{rdp_info[:host]}:#{rdp_info[:port]}" + args += rdp_info[:extra_args] if rdp_info[:extra_args] + when "rdesktop" + args << "-u" << rdp_info[:username] + args << "-p" << rdp_info[:password] if rdp_info[:password] + args += rdp_info[:extra_args] if rdp_info[:extra_args] + args << "#{rdp_info[:host]}:#{rdp_info[:port]}" + end + + # Finally, run the client. + Vagrant::Util::Subprocess.execute(rdp_client, *args) end end end diff --git a/plugins/hosts/slackware/cap/nfs.rb b/plugins/hosts/slackware/cap/nfs.rb index e7f21a094..84413236d 100644 --- a/plugins/hosts/slackware/cap/nfs.rb +++ b/plugins/hosts/slackware/cap/nfs.rb @@ -3,7 +3,7 @@ module VagrantPlugins module Cap class NFS def self.nfs_check_command(env) - "pidof nfsd >/dev/null" + "/sbin/pidof nfsd >/dev/null" end def self.nfs_start_command(env) diff --git a/plugins/hosts/slackware/host.rb b/plugins/hosts/slackware/host.rb index 2afaff8f3..ec3503ac0 100644 --- a/plugins/hosts/slackware/host.rb +++ b/plugins/hosts/slackware/host.rb @@ -4,7 +4,7 @@ module VagrantPlugins module HostSlackware class Host < Vagrant.plugin("2", :host) def detect?(env) - return File.exists?("/etc/slackware-release") || + return File.exists?("/etc/slackware-version") || !Dir.glob("/usr/lib/setup/Plamo-*").empty? end end diff --git a/plugins/hosts/suse/cap/nfs.rb b/plugins/hosts/suse/cap/nfs.rb index 42bde6bb2..789c6cb47 100644 --- a/plugins/hosts/suse/cap/nfs.rb +++ b/plugins/hosts/suse/cap/nfs.rb @@ -7,7 +7,7 @@ module VagrantPlugins end def self.nfs_check_command(env) - "pidof nfsd > /dev/null" + "/sbin/service nfsserver status" end def self.nfs_start_command(env) diff --git a/plugins/hosts/windows/cap/provider_install_virtualbox.rb b/plugins/hosts/windows/cap/provider_install_virtualbox.rb index 160a6bf5d..2179d2164 100644 --- a/plugins/hosts/windows/cap/provider_install_virtualbox.rb +++ b/plugins/hosts/windows/cap/provider_install_virtualbox.rb @@ -2,6 +2,8 @@ require "pathname" require "tempfile" require "vagrant/util/downloader" +require "vagrant/util/file_checksum" +require "vagrant/util/powershell" require "vagrant/util/subprocess" module VagrantPlugins @@ -10,8 +12,9 @@ module VagrantPlugins class ProviderInstallVirtualBox # The URL to download VirtualBox is hardcoded so we can have a # known-good version to download. - URL = "http://download.virtualbox.org/virtualbox/5.0.8/VirtualBox-5.0.8-103449-Win.exe".freeze - VERSION = "5.0.8".freeze + URL = "http://download.virtualbox.org/virtualbox/5.0.10/VirtualBox-5.0.10-104061-Win.exe".freeze + VERSION = "5.0.10".freeze + SHA256SUM = "3e5ed8fe4ada6eef8dfb4fe6fd79fcab4b242acf799f7d3ab4a17b43838b1e04".freeze def self.provider_install_virtualbox(env) tf = Tempfile.new("vagrant") @@ -29,13 +32,22 @@ module VagrantPlugins dl = Vagrant::Util::Downloader.new(URL, tf.path, ui: ui) dl.download! + # Validate that the file checksum matches + actual = Vagrant::Util::FileChecksum.new(tf.path, Digest::SHA2).checksum + if actual != SHA256SUM + raise Vagrant::Errors::ProviderChecksumMismatch, + provider: "virtualbox", + actual: actual, + expected: SHA256SUM + end + # Launch it ui.output(I18n.t( "vagrant.hosts.windows.virtualbox_install_install")) ui.detail(I18n.t( "vagrant.hosts.windows.virtualbox_install_install_detail")) script = File.expand_path("../../scripts/install_virtualbox.ps1", __FILE__) - result = Vagrant::Util::Powershell.execute(script, tf.path) + result = Vagrant::Util::PowerShell.execute(script, tf.path) if result.exit_code != 0 raise Vagrant::Errors::ProviderInstallFailed, provider: "virtualbox", diff --git a/plugins/hosts/windows/cap/ps.rb b/plugins/hosts/windows/cap/ps.rb new file mode 100644 index 000000000..9960f689e --- /dev/null +++ b/plugins/hosts/windows/cap/ps.rb @@ -0,0 +1,42 @@ +require "pathname" +require "tmpdir" + +require "vagrant/util/subprocess" + +module VagrantPlugins + module HostWindows + module Cap + class PS + def self.ps_client(env, ps_info) + logger = Log4r::Logger.new("vagrant::hosts::windows") + + command = <<-EOS + $plain_password = "#{ps_info[:password]}" + $username = "#{ps_info[:username]}" + $port = "#{ps_info[:port]}" + $hostname = "#{ps_info[:host]}" + $password = ConvertTo-SecureString $plain_password -asplaintext -force + $creds = New-Object System.Management.Automation.PSCredential ("$hostname\\$username", $password) + function prompt { kill $PID } + Enter-PSSession -ComputerName $hostname -Credential $creds -Port $port + EOS + + logger.debug("Starting remote powershell with command:\n#{command}") + + args = ["-NoProfile"] + args << "-ExecutionPolicy" + args << "Bypass" + args << "-NoExit" + args << "-EncodedCommand" + args << ::WinRM::PowershellScript.new(command).encoded + if ps_info[:extra_args] + args << ps_info[:extra_args] + end + + # Launch it + Vagrant::Util::Subprocess.execute("powershell", *args) + end + end + end + end +end diff --git a/plugins/hosts/windows/plugin.rb b/plugins/hosts/windows/plugin.rb index d441f229f..84a38836b 100644 --- a/plugins/hosts/windows/plugin.rb +++ b/plugins/hosts/windows/plugin.rb @@ -25,6 +25,11 @@ module VagrantPlugins require_relative "cap/rdp" Cap::RDP end + + host_capability("windows", "ps_client") do + require_relative "cap/ps" + Cap::PS + end end end end diff --git a/plugins/kernel_v1/config/ssh.rb b/plugins/kernel_v1/config/ssh.rb index 7226917ca..7d9b944b5 100644 --- a/plugins/kernel_v1/config/ssh.rb +++ b/plugins/kernel_v1/config/ssh.rb @@ -13,6 +13,7 @@ module VagrantPlugins attr_accessor :private_key_path attr_accessor :forward_agent attr_accessor :forward_x11 + attr_accessor :forward_env attr_accessor :shell def initialize @@ -26,6 +27,7 @@ module VagrantPlugins @private_key_path = UNSET_VALUE @forward_agent = UNSET_VALUE @forward_x11 = UNSET_VALUE + @forward_env = UNSET_VALUE @shell = UNSET_VALUE end @@ -37,6 +39,7 @@ module VagrantPlugins new.ssh.private_key_path = @private_key_path if @private_key_path != UNSET_VALUE new.ssh.forward_agent = @forward_agent if @forward_agent != UNSET_VALUE new.ssh.forward_x11 = @forward_x11 if @forward_x11 != UNSET_VALUE + new.ssh.forward_env = @forward_env if @forward_env != UNSET_VALUE new.ssh.shell = @shell if @shell != UNSET_VALUE end end diff --git a/plugins/kernel_v2/config/ssh.rb b/plugins/kernel_v2/config/ssh.rb index 95603142c..c9e0a6695 100644 --- a/plugins/kernel_v2/config/ssh.rb +++ b/plugins/kernel_v2/config/ssh.rb @@ -7,6 +7,7 @@ module VagrantPlugins class SSHConfig < SSHConnectConfig attr_accessor :forward_agent attr_accessor :forward_x11 + attr_accessor :forward_env attr_accessor :guest_port attr_accessor :keep_alive attr_accessor :shell @@ -22,6 +23,7 @@ module VagrantPlugins @forward_agent = UNSET_VALUE @forward_x11 = UNSET_VALUE + @forward_env = UNSET_VALUE @guest_port = UNSET_VALUE @keep_alive = UNSET_VALUE @proxy_command = UNSET_VALUE @@ -45,6 +47,7 @@ module VagrantPlugins @forward_agent = false if @forward_agent == UNSET_VALUE @forward_x11 = false if @forward_x11 == UNSET_VALUE + @forward_env = false if @forward_env == UNSET_VALUE @guest_port = 22 if @guest_port == UNSET_VALUE @keep_alive = true if @keep_alive == UNSET_VALUE @proxy_command = nil if @proxy_command == UNSET_VALUE diff --git a/plugins/kernel_v2/config/vm.rb b/plugins/kernel_v2/config/vm.rb index 3020611f4..b87e32766 100644 --- a/plugins/kernel_v2/config/vm.rb +++ b/plugins/kernel_v2/config/vm.rb @@ -416,7 +416,8 @@ module VagrantPlugins host_ip: "127.0.0.1", id: "winrm", auto_correct: true - + end + if !@__networks["forwarded_port-winrm-ssl"] network :forwarded_port, guest: 5986, host: 55986, @@ -697,7 +698,7 @@ module VagrantPlugins end end - if options[:ip] && options[:ip].end_with?(".1") + if options[:ip] && options[:ip].end_with?(".1") && options[:type].to_sym != :dhcp machine.ui.warn(I18n.t( "vagrant.config.vm.network_ip_ends_in_one")) end diff --git a/plugins/providers/docker/action.rb b/plugins/providers/docker/action.rb index bb376fb3d..c3f8dfe01 100644 --- a/plugins/providers/docker/action.rb +++ b/plugins/providers/docker/action.rb @@ -156,12 +156,12 @@ module VagrantPlugins b3.use Call, DestroyConfirm do |env3, b4| if env3[:result] b4.use ConfigValidate + b4.use ProvisionerCleanup, :before b4.use EnvSet, force_halt: true b4.use action_halt b4.use HostMachineSyncFoldersDisable b4.use Destroy b4.use DestroyBuildImage - b4.use ProvisionerCleanup else b4.use Message, I18n.t("docker_provider.messages.will_not_destroy") diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb index 64d4bc396..61dec2a1a 100644 --- a/plugins/providers/hyperv/action.rb +++ b/plugins/providers/hyperv/action.rb @@ -38,6 +38,7 @@ module VagrantPlugins end b2.use ConfigValidate + b2.use ProvisionerCleanup, :before b2.use StopInstance b2.use DeleteVM end diff --git a/plugins/providers/hyperv/provider.rb b/plugins/providers/hyperv/provider.rb index d8fee52e7..5db9c880d 100644 --- a/plugins/providers/hyperv/provider.rb +++ b/plugins/providers/hyperv/provider.rb @@ -16,8 +16,9 @@ module VagrantPlugins raise Errors::WindowsRequired end - if !Vagrant::Util::Platform.windows_admin? - raise Errors::AdminRequired + if !Vagrant::Util::Platform.windows_admin? and + !Vagrant::Util::Platform.windows_hyperv_admin? + raise Errors::AdminRequired end if !Vagrant::Util::PowerShell.available? diff --git a/plugins/providers/hyperv/scripts/get_network_config.ps1 b/plugins/providers/hyperv/scripts/get_network_config.ps1 index f8554297c..b8eab5f9a 100644 --- a/plugins/providers/hyperv/scripts/get_network_config.ps1 +++ b/plugins/providers/hyperv/scripts/get_network_config.ps1 @@ -7,9 +7,19 @@ Param( $Dir = Split-Path $script:MyInvocation.MyCommand.Path . ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) +$ip_address = "" $vm = Get-VM -Id $VmId -ErrorAction "Stop" -$network = Get-VMNetworkAdapter -VM $vm -$ip_address = $network.IpAddresses[0] +$networks = Get-VMNetworkAdapter -VM $vm +foreach ($network in $networks) { + if ($network.IpAddresses.Length -gt 0) { + $ip_address = $network.IpAddresses[0] + if (-Not ([string]::IsNullOrEmpty($ip_address))) { + # We found our IP address! + break + } + } +} + $resultHash = @{ ip = "$ip_address" } diff --git a/plugins/providers/hyperv/scripts/get_vm_status.ps1 b/plugins/providers/hyperv/scripts/get_vm_status.ps1 index 739560190..5c1e1aa73 100644 --- a/plugins/providers/hyperv/scripts/get_vm_status.ps1 +++ b/plugins/providers/hyperv/scripts/get_vm_status.ps1 @@ -12,7 +12,7 @@ try { $VM = Get-VM -Id $VmId -ErrorAction "Stop" $State = $VM.state $Status = $VM.status -} catch [Microsoft.HyperV.PowerShell.VirtualizationOperationFailedException] { +} catch [Microsoft.HyperV.PowerShell.VirtualizationException] { $State = "not_created" $Status = $State } diff --git a/plugins/providers/hyperv/scripts/import_vm.ps1 b/plugins/providers/hyperv/scripts/import_vm.ps1 index 3e8664eb2..a994c1cfb 100644 --- a/plugins/providers/hyperv/scripts/import_vm.ps1 +++ b/plugins/providers/hyperv/scripts/import_vm.ps1 @@ -81,14 +81,24 @@ if (!$switchname) { $switchname = (Select-Xml -xml $vmconfig -XPath "//AltSwitchName").node."#text" } -# Determine boot device -Switch ((Select-Xml -xml $vmconfig -XPath "//boot").node.device0."#text") { - "Floppy" { $bootdevice = "floppy" } - "HardDrive" { $bootdevice = "IDE" } - "Optical" { $bootdevice = "CD" } - "Network" { $bootdevice = "LegacyNetworkAdapter" } - "Default" { $bootdevice = "IDE" } -} #switch +if ($generation -eq 1) { + # Determine boot device + Switch ((Select-Xml -xml $vmconfig -XPath "//boot").node.device0."#text") { + "Floppy" { $bootdevice = "Floppy" } + "HardDrive" { $bootdevice = "IDE" } + "Optical" { $bootdevice = "CD" } + "Network" { $bootdevice = "LegacyNetworkAdapter" } + "Default" { $bootdevice = "IDE" } + } #switch +} else { + # Determine boot device + Switch ((Select-Xml -xml $vmconfig -XPath "//boot").node.device0."#text") { + "HardDrive" { $bootdevice = "VHD" } + "Optical" { $bootdevice = "CD" } + "Network" { $bootdevice = "NetworkAdapter" } + "Default" { $bootdevice = "VHD" } + } #switch +} # Determine secure boot options $secure_boot_enabled = (Select-Xml -xml $vmconfig -XPath "//secure_boot_enabled").Node."#text" diff --git a/plugins/providers/virtualbox/action.rb b/plugins/providers/virtualbox/action.rb index 85f52f887..7da0c06ae 100644 --- a/plugins/providers/virtualbox/action.rb +++ b/plugins/providers/virtualbox/action.rb @@ -95,13 +95,14 @@ module VagrantPlugins b2.use Call, DestroyConfirm do |env2, b3| if env2[:result] + b3.use ConfigValidate + b3.use ProvisionerCleanup, :before b3.use CheckAccessible b3.use EnvSet, force_halt: true b3.use action_halt b3.use Destroy b3.use CleanMachineFolder b3.use DestroyUnusedNetworkInterfaces - b3.use ProvisionerCleanup b3.use PrepareNFSValidIds b3.use SyncedFolderCleanup else @@ -217,6 +218,7 @@ module VagrantPlugins b2.use PrepareForwardedPortCollisionParams b2.use HandleForwardedPortCollisions b2.use Resume + b2.use Provision b2.use WaitForCommunicator, [:restoring, :running] else b2.use MessageNotCreated diff --git a/plugins/providers/virtualbox/action/network.rb b/plugins/providers/virtualbox/action/network.rb index cf5ca3b47..9618567ca 100644 --- a/plugins/providers/virtualbox/action/network.rb +++ b/plugins/providers/virtualbox/action/network.rb @@ -148,7 +148,7 @@ module VagrantPlugins def bridged_adapter(config) # Find the bridged interfaces that are available bridgedifs = @env[:machine].provider.driver.read_bridged_interfaces - bridgedifs.delete_if { |interface| interface[:status] == "Down" } + bridgedifs.delete_if { |interface| interface[:status] == "Down" || interface[:status] == "Unknown" } # The name of the chosen bridge interface will be assigned to this # variable. @@ -249,7 +249,7 @@ module VagrantPlugins auto_config: true, mac: nil, nic_type: nil, - type: :static + type: :static, }.merge(options) # Make sure the type is a symbol @@ -290,8 +290,8 @@ module VagrantPlugins # Default subnet prefix length options[:netmask] ||= 64 - # IPv6 we just mask the address and use that as the adapter - options[:adapter_ip] ||= ip.mask(options[:netmask].to_i).to_s + # Set adapter IP to ::1 + options[:adapter_ip] ||= (ip.mask(options[:netmask].to_i) | 1).to_s # Append a 6 to the end of the type options[:type] = "#{options[:type]}6".to_sym diff --git a/plugins/providers/virtualbox/action/network_fix_ipv6.rb b/plugins/providers/virtualbox/action/network_fix_ipv6.rb index eb4ca48ef..7aec8d110 100644 --- a/plugins/providers/virtualbox/action/network_fix_ipv6.rb +++ b/plugins/providers/virtualbox/action/network_fix_ipv6.rb @@ -3,6 +3,7 @@ require "socket" require "log4r" +require "vagrant/util/presence" require "vagrant/util/scoped_hash_override" module VagrantPlugins @@ -12,6 +13,7 @@ module VagrantPlugins # a VM with an IPv6 host-only network will someties lose the # route to that machine. class NetworkFixIPv6 + include Vagrant::Util::Presence include Vagrant::Util::ScopedHashOverride def initialize(app, env) @@ -27,7 +29,8 @@ module VagrantPlugins env[:machine].config.vm.networks.each do |type, options| next if type != :private_network options = scoped_hash_override(options, :virtualbox) - next if options[:ip] == "" + next if options[:ip].to_s.strip == "" + if IPAddr.new(options[:ip]).ipv6? has_v6 = true break @@ -40,16 +43,15 @@ module VagrantPlugins # If we have no IPv6, forget it return if !has_v6 - # We do, so fix them if we must - env[:machine].provider.driver.read_host_only_interfaces.each do |interface| - # Ignore interfaces without an IPv6 address - next if interface[:ipv6] == "" + host_only_interfaces(env).each do |interface| + next if !present?(interface[:ipv6]) + next if interface[:status] != "Up" - # Make the test IP. This is just the highest value IP ip = IPAddr.new(interface[:ipv6]) - ip |= IPAddr.new(":#{":FFFF" * (interface[:ipv6_prefix].to_i / 16)}") + ip |= ("1" * (128 - interface[:ipv6_prefix].to_i)).to_i(2) @logger.info("testing IPv6: #{ip}") + begin UDPSocket.new(Socket::AF_INET6).connect(ip.to_s, 80) rescue Errno::EHOSTUNREACH @@ -58,6 +60,21 @@ module VagrantPlugins end end end + + # The list of interface names for host-only adapters. + # @return [Array] + def host_only_interface_names(env) + env[:machine].provider.driver.read_network_interfaces + .map { |_, i| i[:hostonly] if i[:type] == :hostonly }.compact + end + + # The list of host_only_interfaces that are tied to a host-only adapter. + # @return [Array] + def host_only_interfaces(env) + iface_names = self.host_only_interface_names(env) + env[:machine].provider.driver.read_host_only_interfaces + .select { |interface| iface_names.include?(interface[:name]) } + end end end end diff --git a/plugins/providers/virtualbox/cap.rb b/plugins/providers/virtualbox/cap.rb index 77f8ee1ad..1c0512407 100644 --- a/plugins/providers/virtualbox/cap.rb +++ b/plugins/providers/virtualbox/cap.rb @@ -9,6 +9,8 @@ module VagrantPlugins # # @return [Hash] Host => Guest port mappings. def self.forwarded_ports(machine) + return nil if machine.state.id != :running + {}.tap do |result| machine.provider.driver.read_forwarded_ports.each do |_, _, h, g| result[h] = g diff --git a/plugins/providers/virtualbox/cap/public_address.rb b/plugins/providers/virtualbox/cap/public_address.rb new file mode 100644 index 000000000..09dd5408e --- /dev/null +++ b/plugins/providers/virtualbox/cap/public_address.rb @@ -0,0 +1,15 @@ +module VagrantPlugins + module ProviderVirtualBox + module Cap + module PublicAddress + def self.public_address(machine) + return nil if machine.state.id != :running + + ssh_info = machine.ssh_info + return nil if !ssh_info + ssh_info[:host] + end + end + end + end +end diff --git a/plugins/providers/virtualbox/driver/base.rb b/plugins/providers/virtualbox/driver/base.rb index 8dc0ba994..c304d3efb 100644 --- a/plugins/providers/virtualbox/driver/base.rb +++ b/plugins/providers/virtualbox/driver/base.rb @@ -4,6 +4,7 @@ require 'vagrant/util/busy' require 'vagrant/util/platform' require 'vagrant/util/retryable' require 'vagrant/util/subprocess' +require 'vagrant/util/which' module VagrantPlugins module ProviderVirtualBox @@ -22,16 +23,16 @@ module VagrantPlugins # This flag is used to keep track of interrupted state (SIGINT) @interrupted = false - # Set the path to VBoxManage - @vboxmanage_path = "VBoxManage" - if Vagrant::Util::Platform.windows? || Vagrant::Util::Platform.cygwin? - @logger.debug("Windows. Trying VBOX_INSTALL_PATH for VBoxManage") + @logger.debug("Windows, checking for VBoxManage on PATH first") + @vboxmanage_path = Vagrant::Util::Which.which("VBoxManage") # On Windows, we use the VBOX_INSTALL_PATH environmental # variable to find VBoxManage. - if ENV.key?("VBOX_INSTALL_PATH") || - ENV.key?("VBOX_MSI_INSTALL_PATH") + if !@vboxmanage_path && (ENV.key?("VBOX_INSTALL_PATH") || + ENV.key?("VBOX_MSI_INSTALL_PATH")) + @logger.debug("Windows. Trying VBOX_INSTALL_PATH for VBoxManage") + # Get the path. path = ENV["VBOX_INSTALL_PATH"] || ENV["VBOX_MSI_INSTALL_PATH"] @logger.debug("VBOX_INSTALL_PATH value: #{path}") @@ -51,8 +52,24 @@ module VagrantPlugins end end end + + # If we still don't have one, try to find it using common locations + drive = ENV["SYSTEMDRIVE"] || "C:" + [ + "#{drive}/Program Files/Oracle/VirtualBox", + "#{drive}/Program Files (x86)/Oracle/VirtualBox", + "#{ENV["PROGRAMFILES"]}/Oracle/VirtualBox" + ].each do |maybe| + path = File.join(maybe, "VBoxManage.exe") + if File.file?(path) + @vboxmanage_path = path + break + end + end end + # Fall back to hoping for the PATH to work out + @vboxmanage_path ||= "VBoxManage" @logger.info("VBoxManage path: #{@vboxmanage_path}") end diff --git a/plugins/providers/virtualbox/driver/meta.rb b/plugins/providers/virtualbox/driver/meta.rb index 136ab196a..7aceb9fca 100644 --- a/plugins/providers/virtualbox/driver/meta.rb +++ b/plugins/providers/virtualbox/driver/meta.rb @@ -1,4 +1,5 @@ require "forwardable" +require "thread" require "log4r" @@ -17,6 +18,11 @@ module VagrantPlugins # We use forwardable to do all our driver forwarding extend Forwardable + # We cache the read VirtualBox version here once we have one, + # since during the execution of Vagrant, it likely doesn't change. + @@version = nil + @@version_lock = Mutex.new + # The UUID of the virtual machine we represent attr_reader :uuid @@ -32,19 +38,23 @@ module VagrantPlugins @logger = Log4r::Logger.new("vagrant::provider::virtualbox::meta") @uuid = uuid - # Read and assign the version of VirtualBox we know which - # specific driver to instantiate. - begin - @version = read_version || "" - rescue Vagrant::Errors::CommandUnavailable, - Vagrant::Errors::CommandUnavailableWindows - # This means that VirtualBox was not found, so we raise this - # error here. - raise Vagrant::Errors::VirtualBoxNotDetected + @@version_lock.synchronize do + if !@@version + # Read and assign the version of VirtualBox we know which + # specific driver to instantiate. + begin + @@version = read_version + rescue Vagrant::Errors::CommandUnavailable, + Vagrant::Errors::CommandUnavailableWindows + # This means that VirtualBox was not found, so we raise this + # error here. + raise Vagrant::Errors::VirtualBoxNotDetected + end + end end # Instantiate the proper version driver for VirtualBox - @logger.debug("Finding driver for VirtualBox version: #{@version}") + @logger.debug("Finding driver for VirtualBox version: #{@@version}") driver_map = { "4.0" => Version_4_0, "4.1" => Version_4_1, @@ -53,14 +63,14 @@ module VagrantPlugins "5.0" => Version_5_0, } - if @version.start_with?("4.2.14") + if @@version.start_with?("4.2.14") # VirtualBox 4.2.14 just doesn't work with Vagrant, so show error raise Vagrant::Errors::VirtualBoxBrokenVersion040214 end driver_klass = nil driver_map.each do |key, klass| - if @version.start_with?(key) + if @@version.start_with?(key) driver_klass = klass break end @@ -74,6 +84,7 @@ module VagrantPlugins @logger.info("Using VirtualBox driver: #{driver_klass}") @driver = driver_klass.new(@uuid) + @version = @@version if @uuid # Verify the VM exists, and if it doesn't, then don't worry diff --git a/plugins/providers/virtualbox/driver/version_4_3.rb b/plugins/providers/virtualbox/driver/version_4_3.rb index f895739d3..b77b2a4f9 100644 --- a/plugins/providers/virtualbox/driver/version_4_3.rb +++ b/plugins/providers/virtualbox/driver/version_4_3.rb @@ -610,6 +610,9 @@ module VagrantPlugins 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", diff --git a/plugins/providers/virtualbox/driver/version_5_0.rb b/plugins/providers/virtualbox/driver/version_5_0.rb index d13330c00..75a71c4f1 100644 --- a/plugins/providers/virtualbox/driver/version_5_0.rb +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -611,14 +611,9 @@ module VagrantPlugins def share_folders(folders) folders.each do |folder| hostpath = folder[:hostpath] - -=begin - # Removed for GH-5933 until further research. if Vagrant::Util::Platform.windows? hostpath = Vagrant::Util::Platform.windows_unc_path(hostpath) end -=end - args = ["--name", folder[:name], "--hostpath", diff --git a/plugins/providers/virtualbox/plugin.rb b/plugins/providers/virtualbox/plugin.rb index 84f86ba51..bf355bb6c 100644 --- a/plugins/providers/virtualbox/plugin.rb +++ b/plugins/providers/virtualbox/plugin.rb @@ -34,6 +34,11 @@ module VagrantPlugins Cap end + provider_capability(:virtualbox, :public_address) do + require_relative "cap/public_address" + Cap::PublicAddress + end + provider_capability(:virtualbox, :snapshot_list) do require_relative "cap" Cap diff --git a/plugins/provisioners/ansible/cap/guest/arch/ansible_install.rb b/plugins/provisioners/ansible/cap/guest/arch/ansible_install.rb new file mode 100644 index 000000000..62e1e2141 --- /dev/null +++ b/plugins/provisioners/ansible/cap/guest/arch/ansible_install.rb @@ -0,0 +1,19 @@ + +module VagrantPlugins + module Ansible + module Cap + module Guest + module Arch + module AnsibleInstall + + def self.ansible_install(machine) + machine.communicate.sudo("pacman -Syy --noconfirm") + machine.communicate.sudo("pacman -S --noconfirm ansible") + end + + end + end + end + end + end +end diff --git a/plugins/provisioners/ansible/cap/guest/debian/ansible_install.rb b/plugins/provisioners/ansible/cap/guest/debian/ansible_install.rb new file mode 100644 index 000000000..05308d03d --- /dev/null +++ b/plugins/provisioners/ansible/cap/guest/debian/ansible_install.rb @@ -0,0 +1,28 @@ + +module VagrantPlugins + module Ansible + module Cap + module Guest + module Debian + module AnsibleInstall + + def self.ansible_install(machine) + +install_backports_if_wheezy_release = < /etc/apt/sources.list.d/wheezy-backports.list +fi +INLINE_CRIPT + + machine.communicate.sudo(install_backports_if_wheezy_release) + machine.communicate.sudo("apt-get update -y -qq") + machine.communicate.sudo("apt-get install -y -qq ansible") + end + + end + end + end + end + end +end diff --git a/plugins/provisioners/ansible/cap/guest/fedora/ansible_install.rb b/plugins/provisioners/ansible/cap/guest/fedora/ansible_install.rb new file mode 100644 index 000000000..12c8866b5 --- /dev/null +++ b/plugins/provisioners/ansible/cap/guest/fedora/ansible_install.rb @@ -0,0 +1,26 @@ + +module VagrantPlugins + module Ansible + module Cap + module Guest + module Fedora + module AnsibleInstall + + def self.ansible_install(machine) + if dnf?(machine) + machine.communicate.sudo("dnf -y install ansible") + else + machine.communicate.sudo("yum -y install ansible") + end + end + + def self.dnf?(machine) + machine.communicate.test("/usr/bin/which -s dnf") + end + + end + end + end + end + end +end diff --git a/plugins/provisioners/ansible/cap/guest/freebsd/ansible_install.rb b/plugins/provisioners/ansible/cap/guest/freebsd/ansible_install.rb new file mode 100644 index 000000000..4cf7b1807 --- /dev/null +++ b/plugins/provisioners/ansible/cap/guest/freebsd/ansible_install.rb @@ -0,0 +1,18 @@ + +module VagrantPlugins + module Ansible + module Cap + module Guest + module FreeBSD + module AnsibleInstall + + def self.ansible_install(machine) + machine.communicate.sudo("yes | pkg install ansible") + end + + end + end + end + end + end +end diff --git a/plugins/provisioners/ansible/cap/guest/posix/ansible_installed.rb b/plugins/provisioners/ansible/cap/guest/posix/ansible_installed.rb new file mode 100644 index 000000000..16abae9bf --- /dev/null +++ b/plugins/provisioners/ansible/cap/guest/posix/ansible_installed.rb @@ -0,0 +1,25 @@ +module VagrantPlugins + module Ansible + module Cap + module Guest + module POSIX + module AnsibleInstalled + + # Check if Ansible is installed (at the given version). + # @return [true, false] + def self.ansible_installed(machine, version) + command = 'test -x "$(command -v ansible)"' + + if !version.empty? + command << "&& ansible --version | grep 'ansible #{version}'" + end + + machine.communicate.test(command, sudo: false) + end + + end + end + end + end + end +end diff --git a/plugins/provisioners/ansible/cap/guest/redhat/ansible_install.rb b/plugins/provisioners/ansible/cap/guest/redhat/ansible_install.rb new file mode 100644 index 000000000..eb26e2de3 --- /dev/null +++ b/plugins/provisioners/ansible/cap/guest/redhat/ansible_install.rb @@ -0,0 +1,27 @@ + +module VagrantPlugins + module Ansible + module Cap + module Guest + module RedHat + module AnsibleInstall + + def self.ansible_install(machine) + epel = machine.communicate.execute("#{yum_dnf(machine)} repolist epel | grep -q epel", :error_check => false) + if epel != 0 + machine.communicate.sudo('sudo rpm -i https://dl.fedoraproject.org/pub/epel/epel-release-latest-`rpm -E %dist | sed -n \'s/.*el\([0-9]\).*/\1/p\'`.noarch.rpm') + end + + machine.communicate.sudo("#{yum_dnf(machine)} -y --enablerepo=epel install ansible") + end + + def self.yum_dnf(machine) + machine.communicate.test("/usr/bin/which -s dnf") ? "dnf" : "yum" + end + + end + end + end + end + end +end diff --git a/plugins/provisioners/ansible/cap/guest/suse/ansible_install.rb b/plugins/provisioners/ansible/cap/guest/suse/ansible_install.rb new file mode 100644 index 000000000..64f3cd41c --- /dev/null +++ b/plugins/provisioners/ansible/cap/guest/suse/ansible_install.rb @@ -0,0 +1,18 @@ + +module VagrantPlugins + module Ansible + module Cap + module Guest + module SUSE + module AnsibleInstall + + def self.ansible_install(machine) + machine.communicate.sudo("zypper --non-interactive --quiet install ansible") + end + + end + end + end + end + end +end diff --git a/plugins/provisioners/ansible/cap/guest/ubuntu/ansible_install.rb b/plugins/provisioners/ansible/cap/guest/ubuntu/ansible_install.rb new file mode 100644 index 000000000..92182fe06 --- /dev/null +++ b/plugins/provisioners/ansible/cap/guest/ubuntu/ansible_install.rb @@ -0,0 +1,22 @@ + +module VagrantPlugins + module Ansible + module Cap + module Guest + module Ubuntu + module AnsibleInstall + + def self.ansible_install(machine) + machine.communicate.sudo("apt-get update -y -qq") + machine.communicate.sudo("apt-get install -y -qq software-properties-common python-software-properties") + machine.communicate.sudo("add-apt-repository ppa:ansible/ansible -y") + machine.communicate.sudo("apt-get update -y -qq") + machine.communicate.sudo("apt-get install -y -qq ansible") + end + + end + end + end + end + end +end diff --git a/plugins/provisioners/ansible/config.rb b/plugins/provisioners/ansible/config.rb deleted file mode 100644 index 784cf48f7..000000000 --- a/plugins/provisioners/ansible/config.rb +++ /dev/null @@ -1,128 +0,0 @@ -module VagrantPlugins - module Ansible - class Config < Vagrant.plugin("2", :config) - attr_accessor :playbook - attr_accessor :extra_vars - attr_accessor :inventory_path - attr_accessor :ask_sudo_pass - attr_accessor :ask_vault_pass - attr_accessor :vault_password_file - attr_accessor :limit - attr_accessor :sudo - attr_accessor :sudo_user - attr_accessor :verbose - attr_accessor :tags - attr_accessor :skip_tags - attr_accessor :start_at_task - attr_accessor :groups - attr_accessor :host_key_checking - - # Joker attribute, used to pass unsupported arguments to ansible-playbook anyway - attr_accessor :raw_arguments - # Joker attribute, used to set additional SSH parameters for ansible-playbook anyway - attr_accessor :raw_ssh_args - - def initialize - @playbook = UNSET_VALUE - @extra_vars = UNSET_VALUE - @inventory_path = UNSET_VALUE - @ask_sudo_pass = UNSET_VALUE - @ask_vault_pass = UNSET_VALUE - @vault_password_file = UNSET_VALUE - @limit = UNSET_VALUE - @sudo = UNSET_VALUE - @sudo_user = UNSET_VALUE - @verbose = UNSET_VALUE - @tags = UNSET_VALUE - @skip_tags = UNSET_VALUE - @start_at_task = UNSET_VALUE - @groups = UNSET_VALUE - @host_key_checking = UNSET_VALUE - @raw_arguments = UNSET_VALUE - @raw_ssh_args = UNSET_VALUE - end - - def finalize! - @playbook = nil if @playbook == UNSET_VALUE - @extra_vars = nil if @extra_vars == UNSET_VALUE - @inventory_path = nil if @inventory_path == UNSET_VALUE - @ask_sudo_pass = false unless @ask_sudo_pass == true - @ask_vault_pass = false unless @ask_vault_pass == true - @vault_password_file = nil if @vault_password_file == UNSET_VALUE - @limit = nil if @limit == UNSET_VALUE - @sudo = false unless @sudo == true - @sudo_user = nil if @sudo_user == UNSET_VALUE - @verbose = nil if @verbose == UNSET_VALUE - @tags = nil if @tags == UNSET_VALUE - @skip_tags = nil if @skip_tags == UNSET_VALUE - @start_at_task = nil if @start_at_task == UNSET_VALUE - @groups = {} if @groups == UNSET_VALUE - @host_key_checking = false unless @host_key_checking == true - @raw_arguments = nil if @raw_arguments == UNSET_VALUE - @raw_ssh_args = nil if @raw_ssh_args == UNSET_VALUE - end - - def validate(machine) - errors = _detected_errors - - # Validate that a playbook path was provided - if !playbook - errors << I18n.t("vagrant.provisioners.ansible.no_playbook") - end - - # Validate the existence of said playbook on the host - if playbook - expanded_path = Pathname.new(playbook).expand_path(machine.env.root_path) - if !expanded_path.file? - errors << I18n.t("vagrant.provisioners.ansible.playbook_path_invalid", - path: expanded_path) - end - end - - # Validate that extra_vars is either a hash, or a path to an - # existing file - if extra_vars - extra_vars_is_valid = extra_vars.kind_of?(Hash) || extra_vars.kind_of?(String) - if extra_vars.kind_of?(String) - # Accept the usage of '@' prefix in Vagrantfile (e.g. '@vars.yml' - # and 'vars.yml' are both supported) - match_data = /^@?(.+)$/.match(extra_vars) - extra_vars_path = match_data[1].to_s - expanded_path = Pathname.new(extra_vars_path).expand_path(machine.env.root_path) - extra_vars_is_valid = expanded_path.exist? - if extra_vars_is_valid - @extra_vars = '@' + extra_vars_path - end - end - - if !extra_vars_is_valid - errors << I18n.t("vagrant.provisioners.ansible.extra_vars_invalid", - type: extra_vars.class.to_s, - value: extra_vars.to_s - ) - end - end - - # Validate the existence of the inventory_path, if specified - if inventory_path - expanded_path = Pathname.new(inventory_path).expand_path(machine.env.root_path) - if !expanded_path.exist? - errors << I18n.t("vagrant.provisioners.ansible.inventory_path_invalid", - path: expanded_path) - end - end - - # Validate the existence of the vault_password_file, if specified - if vault_password_file - expanded_path = Pathname.new(vault_password_file).expand_path(machine.env.root_path) - if !expanded_path.exist? - errors << I18n.t("vagrant.provisioners.ansible.vault_password_file_invalid", - path: expanded_path) - end - end - - { "ansible provisioner" => errors } - end - end - end -end diff --git a/plugins/provisioners/ansible/config/base.rb b/plugins/provisioners/ansible/config/base.rb new file mode 100644 index 000000000..14ae1a3e6 --- /dev/null +++ b/plugins/provisioners/ansible/config/base.rb @@ -0,0 +1,119 @@ +module VagrantPlugins + module Ansible + module Config + class Base < Vagrant.plugin("2", :config) + + GALAXY_COMMAND_DEFAULT = "ansible-galaxy install --role-file=%{role_file} --roles-path=%{roles_path} --force".freeze + + attr_accessor :extra_vars + attr_accessor :galaxy_role_file + attr_accessor :galaxy_roles_path + attr_accessor :galaxy_command + attr_accessor :host_vars + attr_accessor :groups + attr_accessor :inventory_path + attr_accessor :limit + attr_accessor :playbook + attr_accessor :raw_arguments + attr_accessor :skip_tags + attr_accessor :start_at_task + attr_accessor :sudo + attr_accessor :sudo_user + attr_accessor :tags + attr_accessor :vault_password_file + attr_accessor :verbose + + def initialize + @extra_vars = UNSET_VALUE + @galaxy_role_file = UNSET_VALUE + @galaxy_roles_path = UNSET_VALUE + @galaxy_command = UNSET_VALUE + @host_vars = UNSET_VALUE + @groups = UNSET_VALUE + @inventory_path = UNSET_VALUE + @limit = UNSET_VALUE + @playbook = UNSET_VALUE + @raw_arguments = UNSET_VALUE + @skip_tags = UNSET_VALUE + @start_at_task = UNSET_VALUE + @sudo = UNSET_VALUE + @sudo_user = UNSET_VALUE + @tags = UNSET_VALUE + @vault_password_file = UNSET_VALUE + @verbose = UNSET_VALUE + end + + def finalize! + @extra_vars = nil if @extra_vars == UNSET_VALUE + @galaxy_role_file = nil if @galaxy_role_file == UNSET_VALUE + @galaxy_roles_path = nil if @galaxy_roles_path == UNSET_VALUE + @galaxy_command = GALAXY_COMMAND_DEFAULT if @galaxy_command == UNSET_VALUE + @host_vars = {} if @host_vars == UNSET_VALUE + @groups = {} if @groups == UNSET_VALUE + @inventory_path = nil if @inventory_path == UNSET_VALUE + @limit = nil if @limit == UNSET_VALUE + @playbook = nil if @playbook == UNSET_VALUE + @raw_arguments = nil if @raw_arguments == UNSET_VALUE + @skip_tags = nil if @skip_tags == UNSET_VALUE + @start_at_task = nil if @start_at_task == UNSET_VALUE + @sudo = false if @sudo != true + @sudo_user = nil if @sudo_user == UNSET_VALUE + @tags = nil if @tags == UNSET_VALUE + @vault_password_file = nil if @vault_password_file == UNSET_VALUE + @verbose = false if @verbose == UNSET_VALUE + 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(machine) + @errors = _detected_errors + + # Validate that a playbook path was provided + if !playbook + @errors << I18n.t("vagrant.provisioners.ansible.errors.no_playbook") + end + + if playbook + check_path_is_a_file(machine, playbook, "vagrant.provisioners.ansible.errors.playbook_path_invalid") + end + + if inventory_path + check_path_exists(machine, inventory_path, "vagrant.provisioners.ansible.errors.inventory_path_invalid") + end + + if galaxy_role_file + check_path_is_a_file(machine, galaxy_role_file, "vagrant.provisioners.ansible.errors.galaxy_role_file_invalid") + end + + if vault_password_file + check_path_is_a_file(machine, vault_password_file, "vagrant.provisioners.ansible.errors.vault_password_file_invalid") + end + + # Validate that extra_vars is either a hash, or a path to an existing file + if extra_vars + extra_vars_is_valid = extra_vars.kind_of?(Hash) || extra_vars.kind_of?(String) + if extra_vars.kind_of?(String) + # Accept the usage of '@' prefix in Vagrantfile (e.g. '@vars.yml' + # and 'vars.yml' are both supported) + match_data = /^@?(.+)$/.match(extra_vars) + extra_vars_path = match_data[1].to_s + extra_vars_is_valid = check_path_is_a_file(machine, extra_vars_path) + if extra_vars_is_valid + @extra_vars = '@' + extra_vars_path + end + end + + if !extra_vars_is_valid + @errors << I18n.t( + "vagrant.provisioners.ansible.errors.extra_vars_invalid", + type: extra_vars.class.to_s, + value: extra_vars.to_s) + end + end + + end + end + end + end +end diff --git a/plugins/provisioners/ansible/config/guest.rb b/plugins/provisioners/ansible/config/guest.rb new file mode 100644 index 000000000..a7f3bf44e --- /dev/null +++ b/plugins/provisioners/ansible/config/guest.rb @@ -0,0 +1,63 @@ +require_relative "base" + +module VagrantPlugins + module Ansible + module Config + class Guest < Base + + attr_accessor :provisioning_path + attr_accessor :tmp_path + attr_accessor :install + attr_accessor :version + + def initialize + super + + @install = UNSET_VALUE + @provisioning_path = UNSET_VALUE + @tmp_path = UNSET_VALUE + @version = UNSET_VALUE + end + + def finalize! + super + + @install = true if @install == UNSET_VALUE + @provisioning_path = "/vagrant" if provisioning_path == UNSET_VALUE + @tmp_path = "/tmp/vagrant-ansible" if tmp_path == UNSET_VALUE + @version = "" if @version == UNSET_VALUE + end + + def validate(machine) + super + + { "ansible local provisioner" => @errors } + end + + protected + + def check_path(machine, path, test_args, error_message_key = nil) + remote_path = Pathname.new(path).expand_path(@provisioning_path) + if machine.communicate.ready? && !machine.communicate.test("test #{test_args} #{remote_path}") + if error_message_key + @errors << I18n.t(error_message_key, path: remote_path, system: "guest") + end + return false + end + # when the machine is not ready for SSH communication, + # the check is "optimistically" by passed. + true + end + + def check_path_is_a_file(machine, path, error_message_key = nil) + check_path(machine, path, "-f", error_message_key) + end + + def check_path_exists(machine, path, error_message_key = nil) + check_path(machine, path, "-e", error_message_key) + end + + end + end + end +end diff --git a/plugins/provisioners/ansible/config/host.rb b/plugins/provisioners/ansible/config/host.rb new file mode 100644 index 000000000..8f9433c63 --- /dev/null +++ b/plugins/provisioners/ansible/config/host.rb @@ -0,0 +1,64 @@ +require_relative "base" + +module VagrantPlugins + module Ansible + module Config + class Host < Base + + attr_accessor :ask_sudo_pass + attr_accessor :ask_vault_pass + attr_accessor :force_remote_user + attr_accessor :host_key_checking + attr_accessor :raw_ssh_args + + def initialize + super + + @ask_sudo_pass = false + @ask_vault_pass = false + @force_remote_user = true + @host_key_checking = false + @raw_ssh_args = UNSET_VALUE + end + + def finalize! + super + + @ask_sudo_pass = false if @ask_sudo_pass != true + @ask_vault_pass = false if @ask_vault_pass != true + @force_remote_user = true if @force_remote_user != false + @host_key_checking = false if @host_key_checking != true + @raw_ssh_args = nil if @raw_ssh_args == UNSET_VALUE + end + + def validate(machine) + super + + { "ansible remote provisioner" => @errors } + end + + protected + + def check_path(machine, path, path_test_method, error_message_key = nil) + expanded_path = Pathname.new(path).expand_path(machine.env.root_path) + if !expanded_path.public_send(path_test_method) + if error_message_key + @errors << I18n.t(error_message_key, path: expanded_path, system: "host") + end + return false + end + true + end + + def check_path_is_a_file(machine, path, error_message_key = nil) + check_path(machine, path, "file?", error_message_key) + end + + def check_path_exists(machine, path, error_message_key = nil) + check_path(machine, path, "exist?", error_message_key) + end + + end + end + end +end diff --git a/plugins/provisioners/ansible/errors.rb b/plugins/provisioners/ansible/errors.rb new file mode 100644 index 000000000..06a1063cf --- /dev/null +++ b/plugins/provisioners/ansible/errors.rb @@ -0,0 +1,27 @@ +require "vagrant" + +module VagrantPlugins + module Ansible + module Errors + class AnsibleError < Vagrant::Errors::VagrantError + error_namespace("vagrant.provisioners.ansible.errors") + end + + class AnsibleCommandFailed < AnsibleError + error_key(:ansible_command_failed) + end + + class AnsibleNotFoundOnHost < AnsibleError + error_key(:ansible_not_found_on_host) + end + + class AnsibleNotFoundOnGuest < AnsibleError + error_key(:ansible_not_found_on_guest) + end + + class AnsibleVersionNotFoundOnGuest < AnsibleError + error_key(:ansible_version_not_found_on_guest) + end + end + end +end \ No newline at end of file diff --git a/plugins/provisioners/ansible/helpers.rb b/plugins/provisioners/ansible/helpers.rb new file mode 100644 index 000000000..1bdb57366 --- /dev/null +++ b/plugins/provisioners/ansible/helpers.rb @@ -0,0 +1,37 @@ +require "vagrant" + +module VagrantPlugins + module Ansible + class Helpers + def self.stringify_ansible_playbook_command(env, command) + shell_command = '' + env.each_pair do |k, v| + if k == 'ANSIBLE_SSH_ARGS' + shell_command += "#{k}='#{v}' " + else + shell_command += "#{k}=#{v} " + end + end + + shell_arg = [] + command.each do |arg| + if arg =~ /(--start-at-task|--limit)=(.+)/ + shell_arg << "#{$1}='#{$2}'" + else + shell_arg << arg + end + end + + shell_command += shell_arg.join(' ') + end + + def self.as_list_argument(v) + v.kind_of?(Array) ? v.join(',') : v + end + + def self.as_array(v) + v.kind_of?(Array) ? v : [v] + end + end + end +end \ No newline at end of file diff --git a/plugins/provisioners/ansible/plugin.rb b/plugins/provisioners/ansible/plugin.rb index 0cafe47fb..b0e0aa0ed 100644 --- a/plugins/provisioners/ansible/plugin.rb +++ b/plugins/provisioners/ansible/plugin.rb @@ -3,21 +3,78 @@ require "vagrant" module VagrantPlugins module Ansible class Plugin < Vagrant.plugin("2") + name "ansible" description <<-DESC - Provides support for provisioning your virtual machines with - Ansible playbooks. + Provides support for provisioning your virtual machines with Ansible + from the Vagrant host (`ansible`) or from the guests (`ansible_local`). DESC - config(:ansible, :provisioner) do - require File.expand_path("../config", __FILE__) - Config + config("ansible", :provisioner) do + require_relative "config/host" + Config::Host end - provisioner(:ansible) do - require File.expand_path("../provisioner", __FILE__) - Provisioner + config("ansible_local", :provisioner) do + require_relative "config/guest" + Config::Guest end + + provisioner("ansible") do + require_relative "provisioner/host" + Provisioner::Host + end + + provisioner("ansible_local") do + require_relative "provisioner/guest" + Provisioner::Guest + end + + guest_capability(:linux, :ansible_installed) do + require_relative "cap/guest/posix/ansible_installed" + Cap::Guest::POSIX::AnsibleInstalled + end + + guest_capability(:freebsd, :ansible_installed) do + require_relative "cap/guest/posix/ansible_installed" + Cap::Guest::POSIX::AnsibleInstalled + end + + guest_capability(:arch, :ansible_install) do + require_relative "cap/guest/arch/ansible_install" + Cap::Guest::Arch::AnsibleInstall + end + + guest_capability(:debian, :ansible_install) do + require_relative "cap/guest/debian/ansible_install" + Cap::Guest::Debian::AnsibleInstall + end + + guest_capability(:ubuntu, :ansible_install) do + require_relative "cap/guest/ubuntu/ansible_install" + Cap::Guest::Ubuntu::AnsibleInstall + end + + guest_capability(:fedora, :ansible_install) do + require_relative "cap/guest/fedora/ansible_install" + Cap::Guest::Fedora::AnsibleInstall + end + + guest_capability(:redhat, :ansible_install) do + require_relative "cap/guest/redhat/ansible_install" + Cap::Guest::RedHat::AnsibleInstall + end + + guest_capability(:suse, :ansible_install) do + require_relative "cap/guest/suse/ansible_install" + Cap::Guest::SUSE::AnsibleInstall + end + + guest_capability(:freebsd, :ansible_install) do + require_relative "cap/guest/freebsd/ansible_install" + Cap::Guest::FreeBSD::AnsibleInstall + end + end end end diff --git a/plugins/provisioners/ansible/provisioner.rb b/plugins/provisioners/ansible/provisioner.rb deleted file mode 100644 index 6dd1313e0..000000000 --- a/plugins/provisioners/ansible/provisioner.rb +++ /dev/null @@ -1,302 +0,0 @@ -require "vagrant/util/platform" -require "thread" - -module VagrantPlugins - module Ansible - class Provisioner < Vagrant.plugin("2", :provisioner) - - @@lock = Mutex.new - - def initialize(machine, config) - super - - @logger = Log4r::Logger.new("vagrant::provisioners::ansible") - end - - def provision - @ssh_info = @machine.ssh_info - - # - # Ansible provisioner options - # - - # By default, connect with Vagrant SSH username - options = %W[--user=#{@ssh_info[:username]}] - - # 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" - - # 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" - - # 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 - options << "--sudo" if config.sudo - options << "--sudo-user=#{config.sudo_user}" if config.sudo_user - options << "#{self.get_verbosity_argument}" if config.verbose - options << "--ask-sudo-pass" if config.ask_sudo_pass - options << "--ask-vault-pass" if config.ask_vault_pass - 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 << "--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 - - env = { - # Ensure Ansible output isn't buffered so that we receive output - # on a task-by-task basis. - "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}", - } - - # 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 - - # Write stdout and stderr data, since it's the regular Ansible output - command << { - env: env, - notify: [:stdout, :stderr], - workdir: @machine.env.root_path.to_s - } - - begin - result = Vagrant::Util::Subprocess.execute(*command) do |type, data| - if type == :stdout || type == :stderr - @machine.env.ui.info(data, new_line: false, prefix: false) - end - end - - raise Vagrant::Errors::AnsibleFailed if result.exit_code != 0 - rescue Vagrant::Util::Subprocess::LaunchError - raise Vagrant::Errors::AnsiblePlaybookAppNotFound - end - end - - protected - - # Auto-generate "safe" inventory file based on Vagrantfile, - # unless inventory_path is explicitly provided - def setup_inventory_file - return config.inventory_path if config.inventory_path - - # Managed machines - inventory_machines = {} - - generated_inventory_dir = @machine.env.local_data_path.join(File.join(%w(provisioners ansible inventory))) - FileUtils.mkdir_p(generated_inventory_dir) unless File.directory?(generated_inventory_dir) - generated_inventory_file = generated_inventory_dir.join('vagrant_ansible_inventory') - - inventory = "# Generated by Vagrant\n\n" - - @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 = [] - - 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 - inventory += "\n[#{gname}]\n" - gmembers.each do |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 - - return generated_inventory_dir.to_s - end - - def get_extra_vars_argument - if config.extra_vars.kind_of?(String) and config.extra_vars =~ /^@.+$/ - # A JSON or YAML file is referenced (requires Ansible 1.3+) - return config.extra_vars - else - # Expected to be a Hash after config validation. (extra_vars as - # JSON requires Ansible 1.2+, while YAML requires Ansible 1.3+) - return config.extra_vars.to_json - end - end - - def get_verbosity_argument - if config.verbose.to_s =~ /^v+$/ - # ansible-playbook accepts "silly" arguments like '-vvvvv' as '-vvvv' for now - return "-#{config.verbose}" - else - # safe default, in case input strays - return '-v' - end - end - - def ansible_ssh_args - @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 - 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 - ssh_options << "-o ForwardAgent=yes" if @ssh_info[:forward_agent] - - # Unchecked SSH Parameters - ssh_options.concat(self.as_array(config.raw_ssh_args)) if config.raw_ssh_args - - # Re-enable ControlPersist Ansible defaults, - # which are lost when ANSIBLE_SSH_ARGS is defined. - unless ssh_options.empty? - ssh_options << "-o ControlMaster=auto" - ssh_options << "-o ControlPersist=60s" - # Intentionally keep ControlPath undefined to let ansible-playbook - # automatically sets this option to Ansible default value - end - - ssh_options.join(' ') - end - - def as_list_argument(v) - v.kind_of?(Array) ? v.join(',') : v - end - - def as_array(v) - v.kind_of?(Array) ? v : [v] - end - - def show_ansible_playbook_command(env, command) - shell_command = '' - env.each_pair do |k, v| - if k == 'ANSIBLE_SSH_ARGS' - shell_command += "#{k}='#{v}' " - else - shell_command += "#{k}=#{v} " - end - end - - shell_arg = [] - command.each do |arg| - if arg =~ /(--start-at-task|--limit)=(.+)/ - shell_arg << "#{$1}='#{$2}'" - else - shell_arg << arg - end - end - - shell_command += shell_arg.join(' ') - - @machine.env.ui.detail(shell_command) - end - end - end -end diff --git a/plugins/provisioners/ansible/provisioner/base.rb b/plugins/provisioners/ansible/provisioner/base.rb new file mode 100644 index 000000000..291df8213 --- /dev/null +++ b/plugins/provisioners/ansible/provisioner/base.rb @@ -0,0 +1,218 @@ +require_relative "../errors" +require_relative "../helpers" + +module VagrantPlugins + module Ansible + module Provisioner + + # This class is a base class where the common functionality shared between + # both Ansible provisioners are stored. + # This is **not an actual provisioner**. + # Instead, {Host} (ansible) or {Guest} (ansible_local) should be used. + + class Base < Vagrant.plugin("2", :provisioner) + + RANGE_PATTERN = %r{(?:\[[a-z]:[a-z]\]|\[[0-9]+?:[0-9]+?\])}.freeze + + protected + + def initialize(machine, config) + super + + @command_arguments = [] + @environment_variables = {} + @inventory_machines = {} + @inventory_path = nil + end + + def prepare_common_command_arguments + # By default we limit by the current machine, + # but this can be overridden by the `limit` option. + if config.limit + @command_arguments << "--limit=#{Helpers::as_list_argument(config.limit)}" + else + @command_arguments << "--limit=#{@machine.name}" + end + + @command_arguments << "--inventory-file=#{inventory_path}" + @command_arguments << "--extra-vars=#{extra_vars_argument}" if config.extra_vars + @command_arguments << "--sudo" if config.sudo + @command_arguments << "--sudo-user=#{config.sudo_user}" if config.sudo_user + @command_arguments << "#{verbosity_argument}" if verbosity_is_enabled? + @command_arguments << "--vault-password-file=#{config.vault_password_file}" if config.vault_password_file + @command_arguments << "--tags=#{Helpers::as_list_argument(config.tags)}" if config.tags + @command_arguments << "--skip-tags=#{Helpers::as_list_argument(config.skip_tags)}" if config.skip_tags + @command_arguments << "--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. + @command_arguments.concat(Helpers::as_array(config.raw_arguments)) if config.raw_arguments + end + + def prepare_common_environment_variables + # Ensure Ansible output isn't buffered so that we receive output + # on a task-by-task basis. + @environment_variables["PYTHONUNBUFFERED"] = 1 + + # 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. + @environment_variables["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.) + @environment_variables["ANSIBLE_NOCOLOR"] = "true" if !@machine.env.ui.color? + end + + # Auto-generate "safe" inventory file based on Vagrantfile, + # unless inventory_path is explicitly provided + def inventory_path + if config.inventory_path + config.inventory_path + else + @inventory_path ||= generate_inventory + end + end + + def get_inventory_host_vars_string(machine_name) + # In Ruby, Symbol and String values are different, but + # Vagrant has to unify them for better user experience. + vars = config.host_vars[machine_name.to_sym] + if !vars + vars = config.host_vars[machine_name.to_s] + end + s = nil + if vars.is_a?(Hash) + s = vars.each.collect{ |k, v| "#{k}=#{v}" }.join(" ") + elsif vars.is_a?(Array) + s = vars.join(" ") + elsif vars.is_a?(String) + s = vars + end + if s and !s.empty? then s else nil end + end + + def generate_inventory + inventory = "# Generated by Vagrant\n\n" + + # This "abstract" step must fill the @inventory_machines list + # and return the list of supported host(s) + inventory += generate_inventory_machines + + inventory += generate_inventory_groups + + # This "abstract" step must create the inventory file and + # return its location path + # TODO: explain possible race conditions, etc. + @inventory_path = ship_generated_inventory(inventory) + end + + # Write out groups information. + # All defined groups will be included, but only supported + # machines and defined child groups will be included. + def generate_inventory_groups + groups_of_groups = {} + defined_groups = [] + group_vars = {} + inventory_groups = "" + + # Verify if host range patterns exist and warn + if config.groups.any? { |gm| gm.to_s[RANGE_PATTERN] } + @machine.ui.warn(I18n.t("vagrant.provisioners.ansible.ansible_host_pattern_detected")) + end + + config.groups.each_pair do |gname, gmembers| + if gname.is_a?(Symbol) + gname = gname.to_s + end + + if gmembers.is_a?(String) + gmembers = gmembers.split(/\s+/) + elsif gmembers.is_a?(Hash) + gmembers = gmembers.each.collect{ |k, v| "#{k}=#{v}" } + elsif !gmembers.is_a?(Array) + gmembers = [] + end + + if gname.end_with?(":children") + groups_of_groups[gname] = gmembers + defined_groups << gname.sub(/:children$/, '') + elsif gname.end_with?(":vars") + group_vars[gname] = gmembers + else + defined_groups << gname + inventory_groups += "\n[#{gname}]\n" + gmembers.each do |gm| + # TODO : Expand and validate host range patterns + # against @inventory_machines list before adding them + # otherwise abort with an error message + if gm[RANGE_PATTERN] + inventory_groups += "#{gm}\n" + end + inventory_groups += "#{gm}\n" if @inventory_machines.include?(gm.to_sym) + end + end + end + + defined_groups.uniq! + groups_of_groups.each_pair do |gname, gmembers| + inventory_groups += "\n[#{gname}]\n" + gmembers.each do |gm| + inventory_groups += "#{gm}\n" if defined_groups.include?(gm) + end + end + + group_vars.each_pair do |gname, gmembers| + if defined_groups.include?(gname.sub(/:vars$/, "")) + inventory_groups += "\n[#{gname}]\n" + gmembers.join("\n") + "\n" + end + end + + return inventory_groups + end + + def extra_vars_argument + if config.extra_vars.kind_of?(String) and config.extra_vars =~ /^@.+$/ + # A JSON or YAML file is referenced. + config.extra_vars + else + # Expected to be a Hash after config validation. + config.extra_vars.to_json + end + end + + def get_galaxy_role_file(basedir) + File.expand_path(config.galaxy_role_file, basedir) + end + + def get_galaxy_roles_path(basedir) + if config.galaxy_roles_path + File.expand_path(config.galaxy_roles_path, basedir) + else + File.join(Pathname.new(config.playbook).expand_path(basedir).parent, 'roles') + end + end + + def ui_running_ansible_command(name, command) + @machine.ui.detail I18n.t("vagrant.provisioners.ansible.running_#{name}") + if verbosity_is_enabled? + # Show the ansible command in use + @machine.env.ui.detail command + end + end + + def verbosity_is_enabled? + config.verbose && !config.verbose.to_s.empty? + end + + def verbosity_argument + if config.verbose.to_s =~ /^-?(v+)$/ + "-#{$+}" + else + # safe default, in case input strays + '-v' + end + end + + end + end + end +end diff --git a/plugins/provisioners/ansible/provisioner/guest.rb b/plugins/provisioners/ansible/provisioner/guest.rb new file mode 100644 index 000000000..833fbfc8b --- /dev/null +++ b/plugins/provisioners/ansible/provisioner/guest.rb @@ -0,0 +1,149 @@ +require 'tempfile' + +require_relative "base" + +module VagrantPlugins + module Ansible + module Provisioner + class Guest < Base + + def initialize(machine, config) + super + @logger = Log4r::Logger.new("vagrant::provisioners::ansible_guest") + end + + def provision + check_and_install_ansible + execute_ansible_galaxy_on_guest if config.galaxy_role_file + execute_ansible_playbook_on_guest + end + + protected + + # + # This handles verifying the Ansible installation, installing it if it was + # requested, and so on. This method will raise exceptions if things are wrong. + # + # Current limitations: + # - The installation of a specific Ansible version is not supported. + # Such feature is difficult to systematically provide via package repositories (apt, yum, ...). + # Installing via pip python packaging or directly from github source would be appropriate, + # but these approaches require more dependency burden. + # - There is no guarantee that the automated installation will replace + # a previous Ansible installation. + # + def check_and_install_ansible + @logger.info("Checking for Ansible installation...") + + # If the guest cannot check if Ansible is installed, + # print a warning and try to continue without any installation attempt... + if !@machine.guest.capability?(:ansible_installed) + @machine.ui.warn(I18n.t("vagrant.provisioners.ansible.cannot_detect")) + return + end + + # Try to install Ansible (if needed and requested) + if config.install && + (config.version.to_s.to_sym == :latest || + !@machine.guest.capability(:ansible_installed, config.version)) + @machine.ui.detail I18n.t("vagrant.provisioners.ansible.installing") + @machine.guest.capability(:ansible_install) + end + + # Check that ansible binaries are well installed on the guest, + @machine.communicate.execute( + "ansible-galaxy --help && ansible-playbook --help", + :error_class => Ansible::Errors::AnsibleNotFoundOnGuest, + :error_key => :ansible_not_found_on_guest) + + # Check if requested ansible version is available + if (!config.version.empty? && + config.version.to_s.to_sym != :latest && + !@machine.guest.capability(:ansible_installed, config.version)) + raise Ansible::Errors::AnsibleVersionNotFoundOnGuest, required_version: config.version.to_s + end + end + + def execute_ansible_galaxy_on_guest + command_values = { + :role_file => get_galaxy_role_file(config.provisioning_path), + :roles_path => get_galaxy_roles_path(config.provisioning_path) + } + remote_command = config.galaxy_command % command_values + + execute_ansible_command_on_guest "galaxy", remote_command + end + + def execute_ansible_playbook_on_guest + prepare_common_command_arguments + prepare_common_environment_variables + + command = (%w(ansible-playbook) << @command_arguments << config.playbook).flatten + remote_command = "cd #{config.provisioning_path} && #{Helpers::stringify_ansible_playbook_command(@environment_variables, command)}" + + execute_ansible_command_on_guest "playbook", remote_command + end + + def execute_ansible_command_on_guest(name, command) + ui_running_ansible_command name, command + + result = execute_on_guest(command) + raise Ansible::Errors::AnsibleCommandFailed if result != 0 + end + + def execute_on_guest(command) + @machine.communicate.execute(command, :error_check => false) do |type, data| + if [:stderr, :stdout].include?(type) + @machine.env.ui.info(data, :new_line => false, :prefix => false) + end + end + end + + def ship_generated_inventory(inventory_content) + inventory_basedir = File.join(config.tmp_path, "inventory") + inventory_path = File.join(inventory_basedir, "vagrant_ansible_local_inventory") + + temp_inventory = Tempfile.new("vagrant_ansible_local_inventory_#{@machine.name}") + temp_inventory.write(inventory_content) + temp_inventory.close + + create_and_chown_remote_folder(inventory_basedir) + @machine.communicate.tap do |comm| + comm.sudo("rm -f #{inventory_path}", error_check: false) + comm.upload(temp_inventory.path, inventory_path) + end + + return inventory_basedir + end + + def generate_inventory_machines + machines = "" + + # TODO: Instead, why not loop over active_machines and skip missing guests, like in Host? + machine.env.machine_names.each do |machine_name| + begin + @inventory_machines[machine_name] = machine_name + if @machine.name == machine_name + machines += "#{machine_name} ansible_connection=local\n" + else + machines += "#{machine_name}\n" + end + host_vars = get_inventory_host_vars_string(machine_name) + machines.sub!(/\n$/, " #{host_vars}\n") if host_vars + end + end + + return machines + end + + def create_and_chown_remote_folder(path) + @machine.communicate.tap do |comm| + comm.sudo("mkdir -p #{path}") + comm.sudo("chown -h #{@machine.ssh_info[:username]} #{path}") + end + end + + end + end + end +end diff --git a/plugins/provisioners/ansible/provisioner/host.rb b/plugins/provisioners/ansible/provisioner/host.rb new file mode 100644 index 000000000..245a00499 --- /dev/null +++ b/plugins/provisioners/ansible/provisioner/host.rb @@ -0,0 +1,257 @@ +require "thread" + +require_relative "base" + +module VagrantPlugins + module Ansible + module Provisioner + class Host < Base + + @@lock = Mutex.new + + def initialize(machine, config) + super + @logger = Log4r::Logger.new("vagrant::provisioners::ansible_host") + end + + def provision + # At this stage, the SSH access is guaranteed to be ready + @ssh_info = @machine.ssh_info + + warn_for_unsupported_platform + execute_ansible_galaxy_from_host if config.galaxy_role_file + execute_ansible_playbook_from_host + end + + protected + + VAGRANT_ARG_SEPARATOR = 'VAGRANT_ARG_SEP' + + def warn_for_unsupported_platform + if Vagrant::Util::Platform.windows? + @machine.env.ui.warn(I18n.t("vagrant.provisioners.ansible.windows_not_supported_for_control_machine")) + end + end + + def prepare_command_arguments + # Connect with native OpenSSH client + # Other modes (e.g. paramiko) are not officially supported, + # but can be enabled via raw_arguments option. + @command_arguments << "--connection=ssh" + + # 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. + @command_arguments << "--timeout=30" + + if !config.force_remote_user + # Pass the vagrant ssh username as Ansible default remote user, because + # the ansible_ssh_user parameter won't be added to the auto-generated inventory. + @command_arguments << "--user=#{@ssh_info[:username]}" + elsif config.inventory_path + # Using an extra variable is the only way to ensure that the Ansible remote user + # is overridden (as the ansible inventory is not under vagrant control) + @command_arguments << "--extra-vars=ansible_ssh_user='#{@ssh_info[:username]}'" + end + + @command_arguments << "--ask-sudo-pass" if config.ask_sudo_pass + @command_arguments << "--ask-vault-pass" if config.ask_vault_pass + + prepare_common_command_arguments + end + + + def prepare_environment_variables + prepare_common_environment_variables + + # Some Ansible options must be passed as environment variables, + # as there is no equivalent command line arguments + @environment_variables["ANSIBLE_HOST_KEY_CHECKING"] = "#{config.host_key_checking}" + + # ANSIBLE_SSH_ARGS is required for Multiple SSH keys, SSH forwarding and custom SSH settings + @environment_variables["ANSIBLE_SSH_ARGS"] = ansible_ssh_args unless ansible_ssh_args.empty? + end + + def execute_command_from_host(command) + begin + result = Vagrant::Util::Subprocess.execute(*command) do |type, data| + if type == :stdout || type == :stderr + @machine.env.ui.detail(data, new_line: false, prefix: false) + end + end + raise Ansible::Errors::AnsibleCommandFailed if result.exit_code != 0 + rescue Vagrant::Errors::CommandUnavailable + raise Ansible::Errors::AnsibleNotFoundOnHost + end + end + + def execute_ansible_galaxy_from_host + command_values = { + :role_file => get_galaxy_role_file(machine.env.root_path), + :roles_path => get_galaxy_roles_path(machine.env.root_path) + } + command_template = config.galaxy_command.gsub(' ', VAGRANT_ARG_SEPARATOR) + str_command = command_template % command_values + + ui_running_ansible_command "galaxy", str_command.gsub(VAGRANT_ARG_SEPARATOR, ' ') + + command = str_command.split(VAGRANT_ARG_SEPARATOR) + command << { + # Write stdout and stderr data, since it's the regular Ansible output + notify: [:stdout, :stderr], + workdir: @machine.env.root_path.to_s + } + + execute_command_from_host command + end + + def execute_ansible_playbook_from_host + prepare_command_arguments + prepare_environment_variables + + # Assemble the full ansible-playbook command + command = (%w(ansible-playbook) << @command_arguments << config.playbook).flatten + + ui_running_ansible_command "playbook", Helpers::stringify_ansible_playbook_command(@environment_variables, command) + + command << { + env: @environment_variables, + # Write stdout and stderr data, since it's the regular Ansible output + notify: [:stdout, :stderr], + workdir: @machine.env.root_path.to_s + } + + execute_command_from_host command + end + + def ship_generated_inventory(inventory_content) + inventory_path = Pathname.new(File.join(@machine.env.local_data_path.join, %w(provisioners ansible inventory))) + FileUtils.mkdir_p(inventory_path) unless File.directory?(inventory_path) + + inventory_file = Pathname.new(File.join(inventory_path, 'vagrant_ansible_inventory')) + @@lock.synchronize do + if !File.exists?(inventory_file) or inventory_content != File.read(inventory_file) + inventory_file.open('w') do |file| + file.write(inventory_content) + end + end + end + + return inventory_path + end + + def generate_inventory_machines + machines = "" + + @machine.env.active_machines.each do |am| + begin + m = @machine.env.machine(*am) + + # Call only once the SSH and WinRM info computation + # Note that machines configured with WinRM communicator, also have a "partial" ssh_info. + m_ssh_info = m.ssh_info + host_vars = get_inventory_host_vars_string(m.name) + if m.config.vm.communicator == :winrm + m_winrm_net_info = CommunicatorWinRM::Helper.winrm_info(m) # can raise a WinRMNotReady exception... + machines += get_inventory_winrm_machine(m, m_winrm_net_info) + machines.sub!(/\n$/, " #{host_vars}\n") if host_vars + @inventory_machines[m.name] = m + elsif !m_ssh_info.nil? + machines += get_inventory_ssh_machine(m, m_ssh_info) + machines.sub!(/\n$/, " #{host_vars}\n") if host_vars + @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 + machines += "# MISSING: '#{m.name}' machine was probably removed without using Vagrant. This machine should be recreated.\n" + end + rescue Vagrant::Errors::MachineNotFound, CommunicatorWinRM::Errors::WinRMNotReady => e + @logger.info("Auto-generated inventory: Skip machine '#{am[0]} (#{am[1]})', which is not configured for this Vagrant environment.") + end + end + + return machines + end + + def get_inventory_ssh_machine(machine, ssh_info) + forced_remote_user = "" + if config.force_remote_user + forced_remote_user = "ansible_ssh_user='#{ssh_info[:username]}' " + end + + "#{machine.name} ansible_ssh_host=#{ssh_info[:host]} ansible_ssh_port=#{ssh_info[:port]} #{forced_remote_user}ansible_ssh_private_key_file='#{ssh_info[:private_key_path][0]}'\n" + end + + def get_inventory_winrm_machine(machine, winrm_net_info) + forced_remote_user = "" + if config.force_remote_user + forced_remote_user = "ansible_ssh_user='#{machine.config.winrm.username}' " + end + + "#{machine.name} ansible_connection=winrm ansible_ssh_host=#{winrm_net_info[:host]} ansible_ssh_port=#{winrm_net_info[:port]} #{forced_remote_user}ansible_ssh_pass='#{machine.config.winrm.password}'\n" + end + + def ansible_ssh_args + @ansible_ssh_args ||= prepare_ansible_ssh_args + end + + def prepare_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 + unless !config.inventory_path && @ssh_info[:private_key_path].size == 1 + @ssh_info[:private_key_path].each do |key| + ssh_options << "-i '#{key}'" + end + end + + # SSH Forwarding + ssh_options << "-o ForwardAgent=yes" if @ssh_info[:forward_agent] + + # Unchecked SSH Parameters + ssh_options.concat(Helpers::as_array(config.raw_ssh_args)) if config.raw_ssh_args + + # Re-enable ControlPersist Ansible defaults, + # which are lost when ANSIBLE_SSH_ARGS is defined. + unless ssh_options.empty? + ssh_options << "-o ControlMaster=auto" + ssh_options << "-o ControlPersist=60s" + # Intentionally keep ControlPath undefined to let ansible-playbook + # automatically sets this option to Ansible default value + end + + ssh_options.join(' ') + end + + end + end + end +end diff --git a/plugins/provisioners/cfengine/cap/debian/cfengine_install.rb b/plugins/provisioners/cfengine/cap/debian/cfengine_install.rb index 57c235654..8d82d5b12 100644 --- a/plugins/provisioners/cfengine/cap/debian/cfengine_install.rb +++ b/plugins/provisioners/cfengine/cap/debian/cfengine_install.rb @@ -7,7 +7,9 @@ module VagrantPlugins machine.communicate.tap do |comm| comm.sudo("mkdir -p #{File.dirname(config.deb_repo_file)} && /bin/echo #{config.deb_repo_line} > #{config.deb_repo_file}") comm.sudo("GPGFILE=`tempfile`; wget -O $GPGFILE #{config.repo_gpg_key_url} && apt-key add $GPGFILE; rm -f $GPGFILE") - + + comm.sudo("apt-get install -y --force-yes apt-transport-https"); + comm.sudo("apt-get update") comm.sudo("apt-get install -y #{config.package_name}") end diff --git a/plugins/provisioners/cfengine/cap/redhat/cfengine_install.rb b/plugins/provisioners/cfengine/cap/redhat/cfengine_install.rb index 828b336d6..f22979388 100644 --- a/plugins/provisioners/cfengine/cap/redhat/cfengine_install.rb +++ b/plugins/provisioners/cfengine/cap/redhat/cfengine_install.rb @@ -14,9 +14,19 @@ module VagrantPlugins logger.info("Installing CFEngine Community Yum Repository GPG KEY from #{config.repo_gpg_key_url}") comm.sudo("GPGFILE=$(mktemp) && wget -O $GPGFILE #{config.repo_gpg_key_url} && rpm --import $GPGFILE; rm -f $GPGFILE") - comm.sudo("yum -y install #{config.package_name}") + if dnf?(machine) + comm.sudo("dnf -y install #{config.package_name}") + else + comm.sudo("yum -y install #{config.package_name}") + end end end + + protected + + def self.dnf?(machine) + machine.communicate.test("/usr/bin/which -s dnf") + end end end end diff --git a/plugins/provisioners/cfengine/config.rb b/plugins/provisioners/cfengine/config.rb index 19d0e7583..5d2ebbef9 100644 --- a/plugins/provisioners/cfengine/config.rb +++ b/plugins/provisioners/cfengine/config.rb @@ -51,7 +51,7 @@ module VagrantPlugins end if @deb_repo_line == UNSET_VALUE - @deb_repo_line = "deb http://cfengine.com/pub/apt/packages stable main" + @deb_repo_line = "deb https://cfengine.com/pub/apt/packages stable main" end @extra_agent_args = nil if @extra_agent_args == UNSET_VALUE @@ -71,7 +71,7 @@ module VagrantPlugins @policy_server_address = nil if @policy_server_address == UNSET_VALUE if @repo_gpg_key_url == UNSET_VALUE - @repo_gpg_key_url = "http://cfengine.com/pub/gpg.key" + @repo_gpg_key_url = "https://cfengine.com/pub/gpg.key" end @upload_path = "/tmp/vagrant-cfengine-file" if @upload_path == UNSET_VALUE @@ -81,7 +81,7 @@ module VagrantPlugins end if @yum_repo_url == UNSET_VALUE - @yum_repo_url = "http://cfengine.com/pub/yum/$basearch" + @yum_repo_url = "https://cfengine.com/pub/yum/$basearch" end if @package_name == UNSET_VALUE diff --git a/plugins/provisioners/chef/cap/debian/chef_install.rb b/plugins/provisioners/chef/cap/debian/chef_install.rb index 3d9302d80..e1cf9470d 100644 --- a/plugins/provisioners/chef/cap/debian/chef_install.rb +++ b/plugins/provisioners/chef/cap/debian/chef_install.rb @@ -5,11 +5,11 @@ module VagrantPlugins module Cap module Debian module ChefInstall - def self.chef_install(machine, version, prerelease, download_path) + def self.chef_install(machine, project, version, channel, options = {}) 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) + command = Omnibus.sh_command(project, version, channel, options) machine.communicate.sudo(command) end end diff --git a/plugins/provisioners/chef/cap/omnios/chef_install.rb b/plugins/provisioners/chef/cap/omnios/chef_install.rb index b20332638..034b2ff5a 100644 --- a/plugins/provisioners/chef/cap/omnios/chef_install.rb +++ b/plugins/provisioners/chef/cap/omnios/chef_install.rb @@ -5,13 +5,13 @@ module VagrantPlugins module Cap module OmniOS module ChefInstall - def self.chef_install(machine, version, prerelease, download_path) - su_cmd = machine.config.solaris.suexec_cmd + def self.chef_install(machine, project, version, channel, options = {}) + su = 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") + machine.communicate.execute("#{su} 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) + command = Omnibus.sh_command(project, version, channel, options) + machine.communicate.execute("#{su} #{command}") end end end diff --git a/plugins/provisioners/chef/cap/redhat/chef_install.rb b/plugins/provisioners/chef/cap/redhat/chef_install.rb index c6aff7bad..f91356d13 100644 --- a/plugins/provisioners/chef/cap/redhat/chef_install.rb +++ b/plugins/provisioners/chef/cap/redhat/chef_install.rb @@ -5,12 +5,22 @@ module VagrantPlugins module Cap module Redhat module ChefInstall - def self.chef_install(machine, version, prerelease, download_path) - machine.communicate.sudo("yum install -y -q curl") + def self.chef_install(machine, project, version, channel, options = {}) + if dnf?(machine) + machine.communicate.sudo("dnf install -y -q curl") + else + machine.communicate.sudo("yum install -y -q curl") + end - command = Omnibus.build_command(version, prerelease, download_path) + command = Omnibus.sh_command(project, version, channel, options) machine.communicate.sudo(command) end + + protected + + def self.dnf?(machine) + machine.communicate.test("/usr/bin/which -s dnf") + end end end end diff --git a/plugins/provisioners/chef/cap/windows/chef_install.rb b/plugins/provisioners/chef/cap/windows/chef_install.rb new file mode 100644 index 000000000..f1f2317a6 --- /dev/null +++ b/plugins/provisioners/chef/cap/windows/chef_install.rb @@ -0,0 +1,16 @@ +require_relative "../../omnibus" + +module VagrantPlugins + module Chef + module Cap + module Windows + module ChefInstall + def self.chef_install(machine, project, version, channel, options = {}) + command = Omnibus.ps_command(project, version, channel, options) + 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 index c95ea8498..b08794833 100644 --- a/plugins/provisioners/chef/cap/windows/chef_installed.rb +++ b/plugins/provisioners/chef/cap/windows/chef_installed.rb @@ -7,7 +7,7 @@ module VagrantPlugins # @return [true, false] def self.chef_installed(machine, version) if version != :latest - command = 'if ((&knife --version) -Match "Chef: "' + version + '"){ exit 0 } else { exit 1 }' + command = 'if ((&knife --version) -Match "Chef: ' + version.to_s + '"){ exit 0 } else { exit 1 }' else command = 'if ((&knife --version) -Match "Chef: *"){ exit 0 } else { exit 1 }' end diff --git a/plugins/provisioners/chef/command_builder.rb b/plugins/provisioners/chef/command_builder.rb index ad5093831..4138ae84f 100644 --- a/plugins/provisioners/chef/command_builder.rb +++ b/plugins/provisioners/chef/command_builder.rb @@ -48,8 +48,12 @@ module VagrantPlugins 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] + + if config.install && (config.version == :latest || config.version >= "11.0") + args << " --force-formatter" + end + args << " #{config.arguments.strip}" if config.arguments args.strip diff --git a/plugins/provisioners/chef/config/base.rb b/plugins/provisioners/chef/config/base.rb index 370736fd6..0fa306449 100644 --- a/plugins/provisioners/chef/config/base.rb +++ b/plugins/provisioners/chef/config/base.rb @@ -1,7 +1,11 @@ +require "vagrant/util/presence" + module VagrantPlugins module Chef module Config class Base < Vagrant.plugin("2", :config) + include Vagrant::Util::Presence + # The path to Chef's bin/ directory. # @return [String] attr_accessor :binary_path @@ -11,6 +15,12 @@ module VagrantPlugins # @return [String] attr_accessor :binary_env + # The name of the Chef project to install. This is "chef" for the Chef + # Client or "chefdk" for the Chef Development Kit. Other product names + # may be available as well. + # @return [String] + attr_accessor :product + # Install Chef on the system if it does not exist. Default is true. # This is a trinary attribute (it can have three values): # @@ -26,9 +36,11 @@ module VagrantPlugins # @return [String, Symbol] attr_accessor :log_level - # Install a prerelease version of Chef. - # @return [true, false] - attr_accessor :prerelease + # The channel from which to download Chef. Currently known values are + # "current" and "stable", but more may be added in the future. The + # default is "current". + # @return [String] + attr_accessor :channel # The version of Chef to install. If Chef is already installed on the # system, the installed version is compared with the requested version. @@ -52,25 +64,38 @@ module VagrantPlugins # @return [String] attr_accessor :installer_download_path + # @deprecated + def prerelease=(value) + STDOUT.puts <<-EOH +[DEPRECATED] The configuration `chef.prerelease' has been deprecated. Please use +`chef.channel' instead. The default value for channel is "current", which +includes prelease versions of Chef Client and the Chef Development Kit. You can +probably just remove the `prerelease' setting from your Vagrantfile and things +will continue working as expected. +EOH + end + def initialize super @binary_path = UNSET_VALUE @binary_env = UNSET_VALUE + @product = UNSET_VALUE @install = UNSET_VALUE @log_level = UNSET_VALUE - @prerelease = UNSET_VALUE + @channel = UNSET_VALUE @version = UNSET_VALUE @installer_download_path = UNSET_VALUE end def finalize! - @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 + @binary_path = nil if @binary_path == UNSET_VALUE + @binary_env = nil if @binary_env == UNSET_VALUE + @product = "chef" if @product == UNSET_VALUE + @install = true if @install == UNSET_VALUE + @log_level = :info if @log_level == UNSET_VALUE + @channel = "current" if @channel == UNSET_VALUE + @version = :latest if @version == UNSET_VALUE @installer_download_path = nil if @installer_download_path == UNSET_VALUE # Make sure the install is a symbol if it's not a boolean @@ -93,18 +118,12 @@ module VagrantPlugins def validate_base(machine) errors = _detected_errors - if missing?(log_level) + if !present?(log_level) errors << I18n.t("vagrant.provisioners.chef.log_level_empty") end errors end - - # 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/provisioners/chef/config/base_runner.rb b/plugins/provisioners/chef/config/base_runner.rb index cfee8e76e..e7bc6cb07 100644 --- a/plugins/provisioners/chef/config/base_runner.rb +++ b/plugins/provisioners/chef/config/base_runner.rb @@ -61,12 +61,6 @@ module VagrantPlugins @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 diff --git a/plugins/provisioners/chef/config/chef_apply.rb b/plugins/provisioners/chef/config/chef_apply.rb index 8f7081875..b28f13c0b 100644 --- a/plugins/provisioners/chef/config/chef_apply.rb +++ b/plugins/provisioners/chef/config/chef_apply.rb @@ -1,9 +1,13 @@ +require "vagrant/util/presence" + require_relative "base" module VagrantPlugins module Chef module Config class ChefApply < Base + include Vagrant::Util::Presence + # The raw recipe text (as a string) to execute via chef-apply. # @return [String] attr_accessor :recipe @@ -30,11 +34,11 @@ module VagrantPlugins def validate(machine) errors = validate_base(machine) - if missing?(recipe) + if !present?(recipe) errors << I18n.t("vagrant.provisioners.chef.recipe_empty") end - if missing?(upload_path) + if !present?(upload_path) errors << I18n.t("vagrant.provisioners.chef.upload_path_empty") end diff --git a/plugins/provisioners/chef/config/chef_client.rb b/plugins/provisioners/chef/config/chef_client.rb index 331cc9b21..343427155 100644 --- a/plugins/provisioners/chef/config/chef_client.rb +++ b/plugins/provisioners/chef/config/chef_client.rb @@ -1,3 +1,4 @@ +require "vagrant/util/presence" require "vagrant/util/which" require_relative "base_runner" @@ -6,6 +7,8 @@ module VagrantPlugins module Chef module Config class ChefClient < BaseRunner + include Vagrant::Util::Presence + # The URL endpoint to the Chef Server. # @return [String] attr_accessor :chef_server_url @@ -55,20 +58,14 @@ module VagrantPlugins def validate(machine) errors = validate_base(machine) - if chef_server_url.to_s.strip.empty? + if !present?(chef_server_url) errors << I18n.t("vagrant.config.chef.server_url_empty") end - if validation_key_path.to_s.strip.empty? + if !present?(validation_key_path) errors << I18n.t("vagrant.config.chef.validation_key_path") end - if delete_client || delete_node - if !Vagrant::Util::Which.which("knife") - errors << I18n.t("vagrant.chef_config_knife_not_found") - end - end - { "chef client provisioner" => errors } end end diff --git a/plugins/provisioners/chef/config/chef_solo.rb b/plugins/provisioners/chef/config/chef_solo.rb index ee4a2ac2e..79bc7ca58 100644 --- a/plugins/provisioners/chef/config/chef_solo.rb +++ b/plugins/provisioners/chef/config/chef_solo.rb @@ -1,9 +1,13 @@ +require "vagrant/util/presence" + require_relative "base_runner" module VagrantPlugins module Chef module Config class ChefSolo < BaseRunner + include Vagrant::Util::Presence + # The path on disk where Chef cookbooks are stored. # Default is "cookbooks". # @return [String] @@ -17,6 +21,10 @@ module VagrantPlugins # @return [String] attr_accessor :environments_path + # The path where nodes are stored on disk. + # @return [String] + attr_accessor :nodes_path + # A URL download a remote recipe from. Note: you should use chef-apply # instead. # @@ -39,36 +47,12 @@ module VagrantPlugins @cookbooks_path = UNSET_VALUE @data_bags_path = UNSET_VALUE @environments_path = UNSET_VALUE + @nodes_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) - puts "DEPRECATION: The 'nfs' setting for the Chef Solo provisioner is" - puts "deprecated. Please use the 'synced_folder_type' setting instead." - puts "The 'nfs' setting will be removed in the next version of Vagrant." - - if value - @synced_folder_type = "nfs" - else - @synced_folder_type = nil - end - end - #------------------------------------------------------------ # Internal methods #------------------------------------------------------------ @@ -86,6 +70,7 @@ module VagrantPlugins end @data_bags_path = [] if @data_bags_path == UNSET_VALUE + @nodes_path = [] if @nodes_path == UNSET_VALUE @roles_path = [] if @roles_path == UNSET_VALUE @environments_path = [] if @environments_path == UNSET_VALUE @environments_path = [@environments_path].flatten @@ -93,6 +78,7 @@ module VagrantPlugins # Make sure the path is an array. @cookbooks_path = prepare_folders_config(@cookbooks_path) @data_bags_path = prepare_folders_config(@data_bags_path) + @nodes_path = prepare_folders_config(@nodes_path) @roles_path = prepare_folders_config(@roles_path) @environments_path = prepare_folders_config(@environments_path) end @@ -100,11 +86,11 @@ module VagrantPlugins def validate(machine) errors = validate_base(machine) - if [cookbooks_path].flatten.compact.empty? + if !present?(Array(cookbooks_path)) errors << I18n.t("vagrant.config.chef.cookbooks_path_empty") end - if environment && environments_path.empty? + if environment && !present?(environments_path) errors << I18n.t("vagrant.config.chef.environment_path_required") end diff --git a/plugins/provisioners/chef/config/chef_zero.rb b/plugins/provisioners/chef/config/chef_zero.rb index d28de3dc9..93a036cd7 100644 --- a/plugins/provisioners/chef/config/chef_zero.rb +++ b/plugins/provisioners/chef/config/chef_zero.rb @@ -1,9 +1,13 @@ +require "vagrant/util/presence" + require_relative "chef_solo" module VagrantPlugins module Chef module Config class ChefZero < BaseRunner + include Vagrant::Util::Presence + # The path on disk where Chef cookbooks are stored. # Default is "cookbooks". # @return [String] @@ -17,6 +21,10 @@ module VagrantPlugins # @return [String] attr_accessor :environments_path + # The path where nodes are stored on disk. + # @return [String] + attr_accessor :nodes_path + # The path where roles are stored on disk. # @return [String] attr_accessor :roles_path @@ -31,6 +39,7 @@ module VagrantPlugins @cookbooks_path = UNSET_VALUE @data_bags_path = UNSET_VALUE @environments_path = UNSET_VALUE + @nodes_path = UNSET_VALUE @roles_path = UNSET_VALUE @synced_folder_type = UNSET_VALUE end @@ -47,6 +56,7 @@ module VagrantPlugins end @data_bags_path = [] if @data_bags_path == UNSET_VALUE + @nodes_path = [] if @nodes_path == UNSET_VALUE @roles_path = [] if @roles_path == UNSET_VALUE @environments_path = [] if @environments_path == UNSET_VALUE @environments_path = [@environments_path].flatten @@ -54,6 +64,7 @@ module VagrantPlugins # Make sure the path is an array. @cookbooks_path = prepare_folders_config(@cookbooks_path) @data_bags_path = prepare_folders_config(@data_bags_path) + @nodes_path = prepare_folders_config(@nodes_path) @roles_path = prepare_folders_config(@roles_path) @environments_path = prepare_folders_config(@environments_path) @@ -62,10 +73,14 @@ module VagrantPlugins def validate(machine) errors = validate_base(machine) - if [cookbooks_path].flatten.compact.empty? + if !present?(Array(cookbooks_path)) errors << I18n.t("vagrant.config.chef.cookbooks_path_empty") end + if !present?(Array(nodes_path)) + errors << I18n.t("vagrant.config.chef.nodes_path_empty") + end + if environment && environments_path.empty? errors << I18n.t("vagrant.config.chef.environment_path_required") end diff --git a/plugins/provisioners/chef/installer.rb b/plugins/provisioners/chef/installer.rb index 16467111c..3dc5efadc 100644 --- a/plugins/provisioners/chef/installer.rb +++ b/plugins/provisioners/chef/installer.rb @@ -3,10 +3,11 @@ module VagrantPlugins 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) + @product = options.fetch(:product) + @channel = options.fetch(:channel) + @version = options.fetch(:version) + @force = options.fetch(:force) + @options = options.dup end # This handles verifying the Chef installation, installing it if it was @@ -28,7 +29,7 @@ module VagrantPlugins @machine.ui.detail(I18n.t("vagrant.chef_installing", version: @version.to_s)) - @machine.guest.capability(:chef_install, @version, @prerelease, @download_path) + @machine.guest.capability(:chef_install, @product, @version, @channel, @options) if !@machine.guest.capability(:chef_installed, @version) raise Provisioner::Base::ChefError, :install_failed diff --git a/plugins/provisioners/chef/omnibus.rb b/plugins/provisioners/chef/omnibus.rb index 5c2eef2fb..897e5282d 100644 --- a/plugins/provisioners/chef/omnibus.rb +++ b/plugins/provisioners/chef/omnibus.rb @@ -1,32 +1,39 @@ module VagrantPlugins module Chef + # Read more about the Omnibus installer here: + # + # https://docs.chef.io/install_omnibus.html + # module Omnibus - OMNITRUCK = "https://www.chef.io/chef/install.sh".freeze + OMNITRUCK = "https://omnitruck.chef.io".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 + def sh_command(project, version, channel, options = {}) + command = "curl -sL #{OMNITRUCK}/install.sh | sudo bash" + command << " -s -- -P \"#{project}\" -c \"#{channel}\"" if version != :latest command << " -v \"#{version}\"" end - if download_path - command << " -d \"#{download_path}\"" + if options[:download_path] + command << " -d \"#{options[:download_path]}\"" end command end - module_function :build_command + module_function :sh_command + + def ps_command(project, version, channel, options = {}) + command = ". { iwr -useb #{OMNITRUCK}/install.ps1 } | iex; install" + command << " -project '#{project}' -channel '#{channel}'" + + if version != :latest + command << " -version '#{version}'" + end + + command + end + module_function :ps_command end end end diff --git a/plugins/provisioners/chef/plugin.rb b/plugins/provisioners/chef/plugin.rb index 540429fad..f7af36138 100644 --- a/plugins/provisioners/chef/plugin.rb +++ b/plugins/provisioners/chef/plugin.rb @@ -53,29 +53,14 @@ module VagrantPlugins 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 + guest_capability(:linux, :chef_installed) do + require_relative "cap/linux/chef_installed" + Cap::Linux::ChefInstalled end guest_capability(:omnios, :chef_install) do @@ -83,6 +68,25 @@ module VagrantPlugins Cap::OmniOS::ChefInstall end + guest_capability(:omnios, :chef_installed) do + require_relative "cap/omnios/chef_installed" + Cap::OmniOS::ChefInstalled + end + + guest_capability(:redhat, :chef_install) do + require_relative "cap/redhat/chef_install" + Cap::Redhat::ChefInstall + end + + guest_capability(:windows, :chef_install) do + require_relative "cap/windows/chef_install" + Cap::Windows::ChefInstall + end + + guest_capability(:windows, :chef_installed) do + require_relative "cap/windows/chef_installed" + Cap::Windows::ChefInstalled + end end end end diff --git a/plugins/provisioners/chef/provisioner/base.rb b/plugins/provisioners/chef/provisioner/base.rb index e0b2b3fc4..1ae4a0808 100644 --- a/plugins/provisioners/chef/provisioner/base.rb +++ b/plugins/provisioners/chef/provisioner/base.rb @@ -1,5 +1,6 @@ require 'tempfile' +require "vagrant/util/presence" require "vagrant/util/template_renderer" require_relative "../installer" @@ -11,6 +12,8 @@ module VagrantPlugins # chef-solo and chef-client provisioning are stored. This is **not an actual # provisioner**. Instead, {ChefSolo} or {ChefServer} should be used. class Base < Vagrant.plugin("2", :provisioner) + include Vagrant::Util::Presence + class ChefError < Vagrant::Errors::VagrantError error_namespace("vagrant.provisioners.chef") end @@ -19,6 +22,22 @@ module VagrantPlugins super @logger = Log4r::Logger.new("vagrant::provisioners::chef") + + if !present?(@config.node_name) + cache = @machine.data_dir.join("chef_node_name") + + if !cache.exist? + @machine.ui.info I18n.t("vagrant.provisioners.chef.generating_node_name") + cache.open("w+") do |f| + f.write("vagrant-#{SecureRandom.hex(4)}") + end + end + + if cache.file? + @logger.info("Loading cached node_name...") + @config.node_name = cache.read.strip + end + end end def install_chef @@ -26,9 +45,10 @@ module VagrantPlugins @logger.info("Checking for Chef installation...") installer = Installer.new(@machine, - force: config.install == :force, - version: config.version, - prerelease: config.prerelease, + product: config.product, + channel: config.channel, + version: config.version, + force: config.install == :force, download_path: config.installer_download_path ) installer.ensure_installed @@ -123,7 +143,7 @@ module VagrantPlugins end def setup_json - @machine.env.ui.info I18n.t("vagrant.provisioners.chef.json") + @machine.ui.info I18n.t("vagrant.provisioners.chef.json") # Get the JSON that we're going to expose to Chef json = @config.json @@ -154,7 +174,7 @@ module VagrantPlugins remote_file = guest_encrypted_data_bag_secret_key_path return if !remote_file - @machine.env.ui.info I18n.t( + @machine.ui.info I18n.t( "vagrant.provisioners.chef.upload_encrypted_data_bag_secret_key") @machine.communicate.tap do |comm| diff --git a/plugins/provisioners/chef/provisioner/chef_client.rb b/plugins/provisioners/chef/provisioner/chef_client.rb index da44fce78..5a9ec918e 100644 --- a/plugins/provisioners/chef/provisioner/chef_client.rb +++ b/plugins/provisioners/chef/provisioner/chef_client.rb @@ -1,6 +1,7 @@ require 'pathname' require 'vagrant' +require 'vagrant/util/presence' require 'vagrant/util/subprocess' require_relative "base" @@ -11,6 +12,8 @@ module VagrantPlugins # This class implements provisioning via chef-client, allowing provisioning # with a chef server. class ChefClient < Base + include Vagrant::Util::Presence + def configure(root_config) raise ChefError, :server_validation_key_required if @config.validation_key_path.nil? raise ChefError, :server_validation_key_doesnt_exist if !File.file?(validation_key_path) @@ -31,8 +34,13 @@ module VagrantPlugins end def cleanup - delete_from_chef_server('client') if @config.delete_client - delete_from_chef_server('node') if @config.delete_node + if @config.delete_node + delete_from_chef_server("node") + end + + if @config.delete_client + delete_from_chef_server("client") + end end def create_client_key_folder @@ -116,28 +124,43 @@ module VagrantPlugins end end + def guest_client_rb_path + File.join(guest_provisioning_path, "client.rb") + end + def guest_validation_key_path File.join(guest_provisioning_path, "validation.pem") end def delete_from_chef_server(deletable) node_name = @config.node_name || @machine.config.vm.hostname - @machine.ui.info(I18n.t( - "vagrant.provisioners.chef.deleting_from_server", + + if !present?(node_name) + @machine.ui.warn(I18n.t("vagrant.provisioners.chef.missing_node_name", + deletable: deletable, + )) + return + end + + @machine.ui.info(I18n.t("vagrant.provisioners.chef.deleting_from_server", deletable: deletable, name: node_name)) - # Knife is not part of the current Vagrant bundle, so it needs to run - # in the context of the system. - Vagrant.global_lock 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 #{node_name}" + command << " --config '#{guest_client_rb_path}'" + command << " --yes" + + output = [] + result = @machine.communicate.sudo(command, error_check: false) do |_, data| + output << data + end + + if result != 0 + @machine.ui.error("There were errors removing the #{deletable} from the Chef Server:") + @machine.ui.error("") + @machine.ui.error(output.join("\n")) + @machine.ui.error("") + @machine.ui.error("Vagrant will continue destroying the virtual machine, but you may need") + @machine.ui.error("to manually delete the #{deletable} from the Chef Server!") end end end diff --git a/plugins/provisioners/chef/provisioner/chef_solo.rb b/plugins/provisioners/chef/provisioner/chef_solo.rb index f3b52f178..a436a8485 100644 --- a/plugins/provisioners/chef/provisioner/chef_solo.rb +++ b/plugins/provisioners/chef/provisioner/chef_solo.rb @@ -19,6 +19,7 @@ module VagrantPlugins attr_reader :environments_folders attr_reader :cookbook_folders + attr_reader :node_folders attr_reader :role_folders attr_reader :data_bags_folders @@ -33,12 +34,14 @@ module VagrantPlugins @role_folders = expanded_folders(@config.roles_path, "roles") @data_bags_folders = expanded_folders(@config.data_bags_path, "data_bags") @environments_folders = expanded_folders(@config.environments_path, "environments") + @node_folders = expanded_folders(@config.nodes_path, "nodes") 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) + share_folders(root_config, "csn", @node_folders, existing) end def provision @@ -101,7 +104,9 @@ module VagrantPlugins end # If we have specified a folder name to append then append it - remote_path += "/#{appended_folder}" if appended_folder + if type == :host + remote_path += "/#{appended_folder}" if appended_folder + end # Append the result results << [type, local_path, remote_path] @@ -158,8 +163,9 @@ module VagrantPlugins { cookbooks_path: guest_paths(@cookbook_folders), recipe_url: @config.recipe_url, + nodes_path: guest_paths(@node_folders), roles_path: guest_paths(@role_folders), - data_bags_path: guest_paths(@data_bags_folders).first, + data_bags_path: guest_paths(@data_bags_folders), environments_path: guest_paths(@environments_folders).first } end diff --git a/plugins/provisioners/chef/provisioner/chef_zero.rb b/plugins/provisioners/chef/provisioner/chef_zero.rb index 48078a09f..70fff6854 100644 --- a/plugins/provisioners/chef/provisioner/chef_zero.rb +++ b/plugins/provisioners/chef/provisioner/chef_zero.rb @@ -44,6 +44,7 @@ module VagrantPlugins local_mode: true, enable_reporting: false, cookbooks_path: guest_paths(@cookbook_folders), + nodes_path: guest_paths(@node_folders), roles_path: guest_paths(@role_folders), data_bags_path: guest_paths(@data_bags_folders).first, environments_path: guest_paths(@environments_folders).first, diff --git a/plugins/provisioners/docker/cap/debian/docker_install.rb b/plugins/provisioners/docker/cap/debian/docker_install.rb index b598ee785..33724f673 100644 --- a/plugins/provisioners/docker/cap/debian/docker_install.rb +++ b/plugins/provisioners/docker/cap/debian/docker_install.rb @@ -3,25 +3,12 @@ module VagrantPlugins module Cap module Debian module DockerInstall - def self.docker_install(machine, version) - package = 'lxc-docker' - package << "-#{version}" if version != :latest - + def self.docker_install(machine) 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 install -y --force-yes -q curl") - 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") + comm.sudo("apt-get update -qq -y") + comm.sudo("apt-get install -qq -y --force-yes curl") + comm.sudo("apt-get purge -qq -y lxc-docker* || true") + comm.sudo("curl -sSL https://get.docker.com/ | sh") end end end diff --git a/plugins/provisioners/docker/cap/fedora/docker_install.rb b/plugins/provisioners/docker/cap/fedora/docker_install.rb new file mode 100644 index 000000000..8feb5b0e2 --- /dev/null +++ b/plugins/provisioners/docker/cap/fedora/docker_install.rb @@ -0,0 +1,27 @@ +module VagrantPlugins + module DockerProvisioner + module Cap + module Fedora + module DockerInstall + def self.docker_install(machine) + machine.communicate.tap do |comm| + if dnf?(machine) + comm.sudo("dnf -y install docker") + else + comm.sudo("yum -y install docker") + end + comm.sudo("systemctl start docker.service") + comm.sudo("systemctl enable docker.service") + end + end + + protected + + def self.dnf?(machine) + machine.communicate.test("/usr/bin/which -s dnf") + end + end + end + end + end +end diff --git a/plugins/provisioners/docker/cap/redhat/docker_install.rb b/plugins/provisioners/docker/cap/redhat/docker_install.rb index 2d6bd1e51..0a6868b8b 100644 --- a/plugins/provisioners/docker/cap/redhat/docker_install.rb +++ b/plugins/provisioners/docker/cap/redhat/docker_install.rb @@ -3,33 +3,32 @@ module VagrantPlugins module Cap module Redhat module DockerInstall - def self.docker_install(machine, version) - if version != :latest - machine.ui.warn(I18n.t("vagrant.docker_install_with_version_not_supported")) + def self.docker_install(machine) + machine.communicate.tap do |comm| + comm.sudo("yum -q -y update") + comm.sudo("yum -q -y remove docker-io* || true") + comm.sudo("curl -sSL https://get.docker.com/ | sh") end case machine.guest.capability("flavor") when :rhel_7 - docker_install_rhel7(machine) + docker_enable_rhel7(machine) else - docker_install_default(machine) + docker_enable_default(machine) end end - def self.docker_install_rhel7(machine) + def self.docker_enable_rhel7(machine) machine.communicate.tap do |comm| - comm.sudo("yum -y install docker") comm.sudo("systemctl start docker.service") comm.sudo("systemctl enable docker.service") end end - def self.docker_install_default(machine) + def self.docker_enable_default(machine) machine.communicate.tap do |comm| - if ! comm.test("rpm -qa | grep epel-release") - comm.sudo("rpm -ivh http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm") - end - comm.sudo("yum -y install docker-io") + comm.sudo("service docker start") + comm.sudo("chkconfig docker on") end end end diff --git a/plugins/provisioners/docker/config.rb b/plugins/provisioners/docker/config.rb index bcf4cf672..8e9de55da 100644 --- a/plugins/provisioners/docker/config.rb +++ b/plugins/provisioners/docker/config.rb @@ -4,11 +4,18 @@ module VagrantPlugins module DockerProvisioner class Config < Vagrant.plugin("2", :config) attr_reader :images - attr_accessor :version + + def version=(value) + STDOUT.puts <<-EOH +[DEPRECATED] The configuration `docker.version' has been deprecated. Docker no +longer allows you to specify the version of Docker you want installed and will +automatically choose the best version for your guest. Please remove this option +from your Vagrantfile. +EOH + end def initialize - @images = Set.new - @version = UNSET_VALUE + @images = Set.new @__build_images = [] @__containers = Hash.new { |h, k| h[k] = {} } @@ -65,9 +72,6 @@ module VagrantPlugins end def finalize! - @version = "latest" if @version == UNSET_VALUE - @version = @version.to_sym - @__containers.each do |name, params| params[:image] ||= name params[:auto_assign_name] = true if !params.key?(:auto_assign_name) diff --git a/plugins/provisioners/docker/installer.rb b/plugins/provisioners/docker/installer.rb index cfcebdd43..3f2ae95ad 100644 --- a/plugins/provisioners/docker/installer.rb +++ b/plugins/provisioners/docker/installer.rb @@ -1,9 +1,8 @@ module VagrantPlugins module DockerProvisioner class Installer - def initialize(machine, version) + def initialize(machine) @machine = machine - @version = version end # This handles verifying the Docker installation, installing it if it was @@ -16,12 +15,12 @@ module VagrantPlugins end if !@machine.guest.capability(:docker_installed) - @machine.ui.detail(I18n.t("vagrant.docker_installing", version: @version.to_s)) - @machine.guest.capability(:docker_install, @version) + @machine.ui.detail(I18n.t("vagrant.docker_installing")) + @machine.guest.capability(:docker_install) + end - if !@machine.guest.capability(:docker_installed) - raise DockerError, :install_failed - end + if !@machine.guest.capability(:docker_installed) + raise DockerError, :install_failed end if @machine.guest.capability?(:docker_configure_vagrant_user) diff --git a/plugins/provisioners/docker/plugin.rb b/plugins/provisioners/docker/plugin.rb index b606729ac..b39422cd0 100644 --- a/plugins/provisioners/docker/plugin.rb +++ b/plugins/provisioners/docker/plugin.rb @@ -24,6 +24,11 @@ module VagrantPlugins Cap::Debian::DockerStartService end + guest_capability("fedora", "docker_install") do + require_relative "cap/fedora/docker_install" + Cap::Fedora::DockerInstall + end + guest_capability("redhat", "docker_install") do require_relative "cap/redhat/docker_install" Cap::Redhat::DockerInstall diff --git a/plugins/provisioners/docker/provisioner.rb b/plugins/provisioners/docker/provisioner.rb index 8b9d609a3..aa4aca27e 100644 --- a/plugins/provisioners/docker/provisioner.rb +++ b/plugins/provisioners/docker/provisioner.rb @@ -11,7 +11,7 @@ module VagrantPlugins def initialize(machine, config, installer = nil, client = nil) super(machine, config) - @installer = installer || Installer.new(@machine, config.version) + @installer = installer || Installer.new(@machine) @client = client || Client.new(@machine) end diff --git a/plugins/provisioners/puppet/config/puppet.rb b/plugins/provisioners/puppet/config/puppet.rb index 440b3b277..47215ca92 100644 --- a/plugins/provisioners/puppet/config/puppet.rb +++ b/plugins/provisioners/puppet/config/puppet.rb @@ -141,7 +141,7 @@ module VagrantPlugins 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) + environmentpath: expanded_path.to_s) end end end diff --git a/plugins/provisioners/puppet/config/puppet_server.rb b/plugins/provisioners/puppet/config/puppet_server.rb index 9c53672d5..5cab589b6 100644 --- a/plugins/provisioners/puppet/config/puppet_server.rb +++ b/plugins/provisioners/puppet/config/puppet_server.rb @@ -2,6 +2,10 @@ module VagrantPlugins module Puppet module Config class PuppetServer < Vagrant.plugin("2", :config) + # The path to Puppet's bin/ directory. + # @return [String] + attr_accessor :binary_path + attr_accessor :client_cert_path attr_accessor :client_private_key_path attr_accessor :facter @@ -12,6 +16,7 @@ module VagrantPlugins def initialize super + @binary_path = UNSET_VALUE @client_cert_path = UNSET_VALUE @client_private_key_path = UNSET_VALUE @facter = {} @@ -29,6 +34,7 @@ module VagrantPlugins def finalize! super + @binary_path = nil if @binary_path == UNSET_VALUE @client_cert_path = nil if @client_cert_path == UNSET_VALUE @client_private_key_path = nil if @client_private_key_path == UNSET_VALUE @puppet_node = nil if @puppet_node == UNSET_VALUE diff --git a/plugins/provisioners/puppet/provisioner/puppet.rb b/plugins/provisioners/puppet/provisioner/puppet.rb index f1987dbd5..ee7320559 100644 --- a/plugins/provisioners/puppet/provisioner/puppet.rb +++ b/plugins/provisioners/puppet/provisioner/puppet.rb @@ -62,12 +62,16 @@ module VagrantPlugins # 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| + @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}/" + @manifest_file.gsub! "$codedir", File.dirname(environments_guest_path) + @manifest_file.gsub! "$environment", @config.environment + if !@manifest_file.start_with? "/" + @manifest_file = File.join(environments_guest_path, @config.environment, @manifest_file) + end @logger.debug("Using manifest from environment.conf: #{@manifest_file}") end end @@ -85,7 +89,7 @@ module VagrantPlugins # 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" + @manifest_file = "#{environments_guest_path}/#{@config.environment}/manifests" parse_environment_metadata end # Check that the shared folders are properly shared @@ -150,7 +154,7 @@ module VagrantPlugins # This is very platform dependent. test_cmd = "sh -c 'command -v #{binary}'" if windows? - test_cmd = "which #{binary}" + test_cmd = "where #{binary}" if @config.binary_path test_cmd = "where \"#{@config.binary_path}:#{binary}\"" end @@ -212,7 +216,7 @@ module VagrantPlugins # If we're on Windows, we need to use the PowerShell style if windows? - facts.map! { |v| "`$env:#{v};" } + facts.map! { |v| "$env:#{v};" } end facter = "#{facts.join(" ")} " diff --git a/plugins/provisioners/puppet/provisioner/puppet_server.rb b/plugins/provisioners/puppet/provisioner/puppet_server.rb index 9c9e9e6b7..29c1c23dc 100644 --- a/plugins/provisioners/puppet/provisioner/puppet_server.rb +++ b/plugins/provisioners/puppet/provisioner/puppet_server.rb @@ -17,8 +17,14 @@ module VagrantPlugins end def verify_binary(binary) + if @config.binary_path + test_cmd = "test -x #{@config.binary_path}/#{binary}" + else + test_cmd = "which #{binary}" + end + @machine.communicate.sudo( - "which #{binary}", + test_cmd, error_class: PuppetServerError, error_key: :not_detected, binary: binary) @@ -83,8 +89,13 @@ module VagrantPlugins facter = "#{facts.join(" ")} " end + + puppet_bin = "puppet" + if @config.binary_path + puppet_bin = File.join(@config.binary_path, puppet_bin) + end options = options.join(" ") - command = "#{facter}puppet agent --onetime --no-daemonize #{options} " + + command = "#{facter} #{puppet_bin} agent --onetime --no-daemonize #{options} " + "--server #{config.puppet_server} --detailed-exitcodes || [ $? -eq 2 ]" @machine.ui.info I18n.t("vagrant.provisioners.puppet_server.running_puppetd") diff --git a/plugins/provisioners/salt/bootstrap-salt.ps1 b/plugins/provisioners/salt/bootstrap-salt.ps1 index 78f268ea3..0ed06f7eb 100644 --- a/plugins/provisioners/salt/bootstrap-salt.ps1 +++ b/plugins/provisioners/salt/bootstrap-salt.ps1 @@ -51,7 +51,7 @@ If ([IntPtr]::Size -eq 4) { # Download minion setup file Write-Host "Downloading Salt minion installer Salt-Minion-$version-$arch-Setup.exe" $webclient = New-Object System.Net.WebClient -$url = "https://docs.saltstack.com/downloads/Salt-Minion-$version-$arch-Setup.exe" +$url = "https://repo.saltstack.com/windows/Salt-Minion-$version-$arch-Setup.exe" $file = "C:\tmp\salt.exe" $webclient.DownloadFile($url, $file) diff --git a/plugins/provisioners/salt/config.rb b/plugins/provisioners/salt/config.rb index 9c564179e..c0e84d932 100644 --- a/plugins/provisioners/salt/config.rb +++ b/plugins/provisioners/salt/config.rb @@ -4,6 +4,10 @@ require "vagrant/util/deep_merge" module VagrantPlugins module Salt class Config < Vagrant.plugin("2", :config) + ## @deprecated + def config_dir=(value) + puts "salt config_dir is deprecated and will be removed in Vagrant 1.9" + end ## salty-vagrant options attr_accessor :minion_config @@ -20,7 +24,6 @@ module VagrantPlugins 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 @@ -33,7 +36,6 @@ module VagrantPlugins attr_accessor :install_args attr_accessor :install_master attr_accessor :install_syndic - attr_accessor :install_command attr_accessor :no_minion attr_accessor :bootstrap_options attr_accessor :version @@ -62,10 +64,8 @@ module VagrantPlugins @install_args = UNSET_VALUE @install_master = UNSET_VALUE @install_syndic = UNSET_VALUE - @install_command = UNSET_VALUE @no_minion = UNSET_VALUE @bootstrap_options = UNSET_VALUE - @config_dir = UNSET_VALUE @masterless = UNSET_VALUE @minion_id = UNSET_VALUE @version = UNSET_VALUE @@ -95,10 +95,8 @@ module VagrantPlugins @install_args = nil if @install_args == UNSET_VALUE @install_master = nil if @install_master == UNSET_VALUE @install_syndic = nil if @install_syndic == UNSET_VALUE - @install_command = nil if @install_command == 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 @@ -111,17 +109,6 @@ 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\\conf" - else - return "/etc/salt" - end - end - def validate(machine) errors = _detected_errors if @minion_config @@ -161,10 +148,6 @@ module VagrantPlugins 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 diff --git a/plugins/provisioners/salt/provisioner.rb b/plugins/provisioners/salt/provisioner.rb index 72365776e..f46d8fe16 100644 --- a/plugins/provisioners/salt/provisioner.rb +++ b/plugins/provisioners/salt/provisioner.rb @@ -35,8 +35,8 @@ module VagrantPlugins desired_binaries = [] if !@config.no_minion if @machine.config.vm.communicator == :winrm - desired_binaries.push('C:\\salt\\salt-minion.exe') - desired_binaries.push('C:\\salt\\salt-call.exe') + desired_binaries.push('C:\\salt\\salt-minion.bat') + desired_binaries.push('C:\\salt\\salt-call.bat') else desired_binaries.push('salt-minion') desired_binaries.push('salt-call') @@ -143,13 +143,6 @@ 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 @@ -329,11 +322,10 @@ module VagrantPlugins def call_masterless @machine.env.ui.info "Calling state.highstate in local mode... (this may take a while)" - cmd = "salt-call state.highstate --local" + cmd = "salt-call state.highstate --local#{get_loglevel}#{get_colorize}#{get_pillar}" 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) @@ -342,11 +334,6 @@ module VagrantPlugins end def call_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 @@ -361,8 +348,8 @@ module VagrantPlugins else if @machine.config.vm.communicator == :winrm opts = { elevated: true } - @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| + @machine.communicate.execute("C:\\salt\\salt-call.bat saltutil.sync_all", opts) + @machine.communicate.execute("C:\\salt\\salt-call.bat state.highstate --retcode-passthrough #{get_loglevel}#{get_colorize}#{get_pillar}", opts) do |type, data| if @config.verbose @machine.env.ui.info(data.rstrip) end diff --git a/plugins/provisioners/shell/config.rb b/plugins/provisioners/shell/config.rb index 2f4957214..d5f260b91 100644 --- a/plugins/provisioners/shell/config.rb +++ b/plugins/provisioners/shell/config.rb @@ -5,6 +5,7 @@ module VagrantPlugins class Config < Vagrant.plugin("2", :config) attr_accessor :inline attr_accessor :path + attr_accessor :env attr_accessor :upload_path attr_accessor :args attr_accessor :privileged @@ -12,29 +13,34 @@ module VagrantPlugins attr_accessor :keep_color attr_accessor :name attr_accessor :powershell_args + attr_accessor :powershell_elevated_interactive def initialize - @args = UNSET_VALUE - @inline = UNSET_VALUE - @path = UNSET_VALUE - @upload_path = UNSET_VALUE - @privileged = UNSET_VALUE - @binary = UNSET_VALUE - @keep_color = UNSET_VALUE - @name = UNSET_VALUE - @powershell_args = UNSET_VALUE + @args = UNSET_VALUE + @inline = UNSET_VALUE + @path = UNSET_VALUE + @env = UNSET_VALUE + @upload_path = UNSET_VALUE + @privileged = UNSET_VALUE + @binary = UNSET_VALUE + @keep_color = UNSET_VALUE + @name = UNSET_VALUE + @powershell_args = UNSET_VALUE + @powershell_elevated_interactive = UNSET_VALUE end def finalize! - @args = nil if @args == UNSET_VALUE - @inline = nil if @inline == UNSET_VALUE - @path = nil if @path == UNSET_VALUE - @upload_path = "/tmp/vagrant-shell" if @upload_path == UNSET_VALUE - @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 + @args = nil if @args == UNSET_VALUE + @inline = nil if @inline == UNSET_VALUE + @path = nil if @path == UNSET_VALUE + @env = {} if @env == UNSET_VALUE + @upload_path = "/tmp/vagrant-shell" if @upload_path == UNSET_VALUE + @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 + @powershell_elevated_interactive = false if @powershell_elevated_interactive == UNSET_VALUE if @args && args_valid? @args = @args.is_a?(Array) ? @args.map { |a| a.to_s } : @args.to_s @@ -69,6 +75,10 @@ module VagrantPlugins end end + if !env.is_a?(Hash) + errors << I18n.t("vagrant.provisioners.shell.env_must_be_a_hash") + end + # There needs to be a path to upload the script to if !upload_path errors << I18n.t("vagrant.provisioners.shell.upload_path_not_set") @@ -78,6 +88,10 @@ module VagrantPlugins errors << I18n.t("vagrant.provisioners.shell.args_bad_type") end + if powershell_elevated_interactive && !privileged + errors << I18n.t("vagrant.provisioners.shell.interactive_not_elevated") + end + { "shell provisioner" => errors } end diff --git a/plugins/provisioners/shell/provisioner.rb b/plugins/provisioners/shell/provisioner.rb index 1b89d1f29..a04d80c92 100644 --- a/plugins/provisioners/shell/provisioner.rb +++ b/plugins/provisioners/shell/provisioner.rb @@ -47,7 +47,12 @@ module VagrantPlugins # This is the provision method called if SSH is what is running # on the remote end, which assumes a POSIX-style host. def provision_ssh(args) - command = "chmod +x #{config.upload_path} && #{config.upload_path}#{args}" + env = config.env.map { |k,v| "#{k}=#{quote_and_escape(v)}" }.join(" ") + + command = "chmod +x '#{config.upload_path}'" + command << " &&" + command << " #{env}" if !env.empty? + command << " #{config.upload_path}#{args}" with_script_file do |path| # Upload the script to the machine @@ -107,6 +112,9 @@ module VagrantPlugins # Upload it comm.upload(path.to_s, upload_path) + # Build the environment + env = config.env.map { |k,v| "$env:#{k} = #{quote_and_escape(v)}" }.join("; ") + # Calculate the path that we'll be executing exec_path = upload_path exec_path.gsub!('/', '\\') @@ -125,6 +133,11 @@ module VagrantPlugins command = "powershell #{shell_args.to_s} -file #{command}" if File.extname(exec_path).downcase == '.ps1' + # Append the environment + if !env.empty? + command = "#{env}; #{command}" + end + if config.name @machine.ui.detail(I18n.t("vagrant.provisioners.shell.running", script: "script: #{config.name}")) @@ -137,7 +150,7 @@ module VagrantPlugins end # Execute it with sudo - comm.sudo(command, elevated: config.privileged) do |type, data| + comm.sudo(command, { elevated: config.privileged, interactive: config.powershell_elevated_interactive }) do |type, data| handle_comm(type, data) end end diff --git a/plugins/pushes/ftp/push.rb b/plugins/pushes/ftp/push.rb index 7a69c4f02..755304ee6 100644 --- a/plugins/pushes/ftp/push.rb +++ b/plugins/pushes/ftp/push.rb @@ -19,7 +19,7 @@ module VagrantPlugins # 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)) + destination = File.join(config.destination, relative_path) file = File.expand_path(file, env.root_path) [file, destination] end] diff --git a/plugins/pushes/heroku/push.rb b/plugins/pushes/heroku/push.rb index b894eba81..a0e26df8a 100644 --- a/plugins/pushes/heroku/push.rb +++ b/plugins/pushes/heroku/push.rb @@ -69,7 +69,8 @@ module VagrantPlugins result = execute!("git", "--git-dir", git_dir(path), "--work-tree", path, - "branch", + "symbolic-ref", + "HEAD", ) # Returns something like "* master" diff --git a/plugins/pushes/local-exec/config.rb b/plugins/pushes/local-exec/config.rb index 747ff8925..d62dfa8fa 100644 --- a/plugins/pushes/local-exec/config.rb +++ b/plugins/pushes/local-exec/config.rb @@ -10,14 +10,24 @@ module VagrantPlugins # @return [String] attr_accessor :inline + # The arguments to provide when executing the script. + # @return [Array] + attr_accessor :args + def initialize @script = UNSET_VALUE @inline = UNSET_VALUE + @args = UNSET_VALUE end def finalize! @script = nil if @script == UNSET_VALUE @inline = nil if @inline == UNSET_VALUE + @args = nil if @args == UNSET_VALUE + + if @args && args_valid? + @args = @args.is_a?(Array) ? @args.map { |a| a.to_s } : @args.to_s + end end def validate(machine) @@ -33,6 +43,10 @@ module VagrantPlugins errors << I18n.t("local_exec_push.errors.cannot_specify_script_and_inline") end + if !args_valid? + errors << I18n.t("local_exec_push.errors.args_bad_type") + end + { "Local Exec push" => errors } end @@ -43,6 +57,21 @@ module VagrantPlugins def missing?(obj) obj.to_s.strip.empty? end + + # Args are optional, but if they're provided we only support them as a + # string or as an array. + def args_valid? + return true if !args + return true if args.is_a?(String) + return true if args.is_a?(Fixnum) + if args.is_a?(Array) + args.each do |a| + return false if !a.kind_of?(String) && !a.kind_of?(Fixnum) + end + + return true + end + end end end end diff --git a/plugins/pushes/local-exec/locales/en.yml b/plugins/pushes/local-exec/locales/en.yml index 08808a879..3ae196d9c 100644 --- a/plugins/pushes/local-exec/locales/en.yml +++ b/plugins/pushes/local-exec/locales/en.yml @@ -20,3 +20,4 @@ en: config.push.define "local-exec" do |push| push.%{attribute} = "..." end + args_bad_type: "Local-exec push `args` must be a string or array." \ No newline at end of file diff --git a/plugins/pushes/local-exec/push.rb b/plugins/pushes/local-exec/push.rb index 718b1ab04..503957c9e 100644 --- a/plugins/pushes/local-exec/push.rb +++ b/plugins/pushes/local-exec/push.rb @@ -9,20 +9,20 @@ module VagrantPlugins class Push < Vagrant.plugin("2", :push) def push if config.inline - execute_inline!(config.inline) + execute_inline!(config.inline, config.args) else - execute_script!(config.script) + execute_script!(config.script, config.args) end end # Execute the inline script by writing it to a tempfile and executing. - def execute_inline!(inline) + def execute_inline!(inline, args) script = Tempfile.new(["vagrant-local-exec-script", ".sh"]) script.write(inline) script.rewind script.close - execute_script!(script.path) + execute_script!(script.path, args) ensure if script script.close @@ -31,16 +31,54 @@ module VagrantPlugins end # Execute the script, expanding the path relative to the current env root. - def execute_script!(path) + def execute_script!(path, args) path = File.expand_path(path, env.root_path) FileUtils.chmod("+x", path) - execute!(path) + + if args.is_a?(String) + args = " #{args.to_s}" + elsif args.is_a?(Array) + args = args.map { |a| quote_and_escape(a) } + args = " #{args.join(" ")}" + end + + execute!("#{path}#{args}") end # Execute the script, raising an exception if it fails. def execute!(*cmd) + if Vagrant::Util::Platform.windows? + execute_subprocess!(*cmd) + else + execute_exec!(*cmd) + end + end + + private + + # Quote and escape strings for shell execution, thanks to Capistrano. + def quote_and_escape(text, quote = '"') + "#{quote}#{text.gsub(/#{quote}/) { |m| "#{m}\\#{m}#{m}" }}#{quote}" + end + + # Run the command as exec (unix). + def execute_exec!(*cmd) Vagrant::Util::SafeExec.exec(cmd[0], *cmd[1..-1]) end + + # Run the command as a subprocess (windows). + def execute_subprocess!(*cmd) + cmd = cmd.dup << { notify: [:stdout, :stderr] } + result = Vagrant::Util::Subprocess.execute(*cmd) do |type, data| + if type == :stdout + @env.ui.info(data, new_line: false) + elsif type == :stderr + @env.ui.warn(data, new_line: false) + end + end + + Kernel.exit(result.exit_code) + end end end end diff --git a/plugins/synced_folders/rsync/command/rsync_auto.rb b/plugins/synced_folders/rsync/command/rsync_auto.rb index 097e68cc3..407b7f368 100644 --- a/plugins/synced_folders/rsync/command/rsync_auto.rb +++ b/plugins/synced_folders/rsync/command/rsync_auto.rb @@ -185,7 +185,10 @@ module VagrantPlugins ssh_info = opts[:machine].ssh_info begin + start = Time.now RsyncHelper.rsync_single(opts[:machine], ssh_info, opts[:opts]) + finish = Time.now + @logger.info("Time spent in rsync: #{finish-start} (in seconds)") rescue Vagrant::Errors::MachineGuestNotReady # Error communicating to the machine, probably a reload or # halt is happening. Just notify the user but don't fail out. diff --git a/plugins/synced_folders/rsync/helper.rb b/plugins/synced_folders/rsync/helper.rb index 594410436..495cb7e5d 100644 --- a/plugins/synced_folders/rsync/helper.rb +++ b/plugins/synced_folders/rsync/helper.rb @@ -38,6 +38,11 @@ module VagrantPlugins hostpath = File.expand_path(hostpath, machine.env.root_path) hostpath = Vagrant::Util::Platform.fs_real_path(hostpath).to_s + # if the guest has a guest path scrubber capability, use it + if machine.guest.capability?(:rsync_scrub_guestpath) + guestpath = machine.guest.capability(:rsync_scrub_guestpath, opts) + end + if Vagrant::Util::Platform.windows? # rsync for Windows expects cygwin style paths, always. hostpath = Vagrant::Util::Platform.cygwin_path(hostpath) @@ -61,9 +66,17 @@ module VagrantPlugins proxy_command = "-o ProxyCommand='#{ssh_info[:proxy_command]}' " end + # Create the path for the control sockets. We used to do this + # in the machine data dir but this can result in paths that are + # too long for unix domain sockets. + controlpath = File.join(Dir.tmpdir, "ssh.#{rand(1000)}") + rsh = [ "ssh -p #{ssh_info[:port]} " + proxy_command + + "-o ControlMaster=auto " + + "-o ControlPath=#{controlpath} " + + "-o ControlPersist=10m " + "-o StrictHostKeyChecking=no " + "-o IdentitiesOnly=true " + "-o UserKnownHostsFile=/dev/null", diff --git a/plugins/synced_folders/smb/scripts/host_info.ps1 b/plugins/synced_folders/smb/scripts/host_info.ps1 index 0e089932e..c4f13aada 100644 --- a/plugins/synced_folders/smb/scripts/host_info.ps1 +++ b/plugins/synced_folders/smb/scripts/host_info.ps1 @@ -1,11 +1,21 @@ -$ErrorAction = "Stop" - -$net = Get-NetIPAddress | Where-Object { - ($_.IPAddress -ne "127.0.0.1") -and ($_.IPAddress -ne "::1") -} | Sort-Object $_.AddressFamily - -$result = @{ - ip_addresses = $net.IPAddress -} - -Write-Output $(ConvertTo-Json $result) +$ErrorAction = "Stop" + +# Find all of the NICsq +$nics = [System.Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces() + +# Save the IP addresses somewhere +$nic_ip_addresses = @() + +foreach ($nic in $nics) { + $nic_ip_addresses += $nic.GetIPProperties().UnicastAddresses | Where-Object { + ($_.Address.IPAddressToString -ne "127.0.0.1") -and ($_.Address.IPAddressToString -ne "::1") + } | Select -ExpandProperty Address +} + +$nic_ip_addresses = $nic_ip_addresses | Sort-Object $_.AddressFamily + +$result = @{ + ip_addresses = $nic_ip_addresses.IPAddressToString +} + +Write-Output $(ConvertTo-Json $result) diff --git a/plugins/synced_folders/smb/synced_folder.rb b/plugins/synced_folders/smb/synced_folder.rb index e3f5a1af2..e74d2fbbf 100644 --- a/plugins/synced_folders/smb/synced_folder.rb +++ b/plugins/synced_folders/smb/synced_folder.rb @@ -92,6 +92,11 @@ module VagrantPlugins guest: machine.guest.name.to_s end + # Setup if we have it + if machine.guest.capability?(:smb_install) + machine.guest.capability(:smb_install) + end + # Detect the host IP for this guest if one wasn't specified # for every folder. host_ip = nil diff --git a/templates/commands/ssh_config/config.erb b/templates/commands/ssh_config/config.erb index 2cdb2dbf4..42b9a8f38 100644 --- a/templates/commands/ssh_config/config.erb +++ b/templates/commands/ssh_config/config.erb @@ -6,11 +6,7 @@ Host <%= host_key %> StrictHostKeyChecking no PasswordAuthentication no <% private_key_path.each do |path| %> -<% if path.include?(" ") -%> - IdentityFile "<%= path %>" -<% else -%> - IdentityFile <%= path %> -<% end -%> + IdentityFile <%= path.inspect %> <% end -%> IdentitiesOnly yes LogLevel FATAL diff --git a/templates/guests/slackware/network_dhcp.erb b/templates/guests/slackware/network_dhcp.erb new file mode 100644 index 000000000..53f48f8c0 --- /dev/null +++ b/templates/guests/slackware/network_dhcp.erb @@ -0,0 +1,23 @@ +IPADDR[0]="" +NETMASK[0]="" +USE_DHCP[0]="yes" +DHCP_HOSTNAME[0]="" + +IPADDR[1]="" +NETMASK[1]="" +USE_DHCP[1]="yes" +DHCP_HOSTNAME[1]="" + +IPADDR[2]="" +NETMASK[2]="" +USE_DHCP[2]="" +DHCP_HOSTNAME[2]="" + +IPADDR[3]="" +NETMASK[3]="" +USE_DHCP[3]="" +DHCP_HOSTNAME[3]="" + +GATEWAY="" + +DEBUG_ETH_UP="no" diff --git a/templates/guests/slackware/network_static.erb b/templates/guests/slackware/network_static.erb new file mode 100644 index 000000000..0745e8b1a --- /dev/null +++ b/templates/guests/slackware/network_static.erb @@ -0,0 +1,25 @@ +IPADDR[0]="" +NETMASK[0]="" +USE_DHCP[0]="yes" +DHCP_HOSTNAME[0]="" + +IPADDR[1]="<%= options[:ip] %>" +NETMASK[1]="" +USE_DHCP[1]="" +DHCP_HOSTNAME[1]="" + +IPADDR[2]="" +NETMASK[2]="" +USE_DHCP[2]="" +DHCP_HOSTNAME[2]="" + +IPADDR[3]="" +NETMASK[3]="" +USE_DHCP[3]="" +DHCP_HOSTNAME[3]="" + +<% if options[:gateway] %> + GATEWAY="<%= options[:gateway] %>" +<% end %> + +DEBUG_ETH_UP="no" diff --git a/templates/guests/suse/network_dhcp.erb b/templates/guests/suse/network_dhcp.erb index 8bbaa62e4..3504b265e 100644 --- a/templates/guests/suse/network_dhcp.erb +++ b/templates/guests/suse/network_dhcp.erb @@ -1,6 +1,6 @@ #VAGRANT-BEGIN # The contents below are automatically generated by Vagrant. Do not modify. -BOOTPROTO=dhcp -ONBOOT=yes -DEVICE=eth<%= options[:interface] %> +BOOTPROTO='dhcp' +STARTMODE='auto' +DEVICE='eth<%= options[:interface] %>' #VAGRANT-END diff --git a/templates/locales/command_ps.yml b/templates/locales/command_ps.yml new file mode 100644 index 000000000..179aae842 --- /dev/null +++ b/templates/locales/command_ps.yml @@ -0,0 +1,27 @@ +en: + vagrant_ps: + detecting: |- + Detecting if a remote PowerShell connection can be made with the guest... + reseting: |- + Resetting WinRM TrustedHosts to their original value. + + errors: + host_unsupported: |- + Your host does not support PowerShell. A remote PowerShell connection + can only be made from a windows host. + + ps_remoting_undetected: |- + Unable to establish a remote PowerShell connection with the guest. + Check if the firewall rules on the guest allow connections to the + Windows remote management service. + + powershell_error: |- + An error occurred while executing a PowerShell script. This error + is shown below. Please read the error message and see if this is + a configuration error with your system. If it is not, then please + report a bug. + + Script: %{script} + Error: + + %{stderr} diff --git a/templates/locales/en.yml b/templates/locales/en.yml old mode 100644 new mode 100755 index 16c8b4e5d..3f1a3ab94 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -123,14 +123,8 @@ en: installed and attempt to continue. docker_configure_autostart: |- Configuring Docker to autostart containers... - docker_install_with_version_not_supported: |- - 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... + Installing Docker onto machine... docker_pulling_images: Pulling Docker images... docker_pulling_single: |- @@ -166,6 +160,8 @@ en: description of what they do. %{list} + guest_deb_installing_smb: |- + Installing SMB "mount.cifs"... global_status_footer: |- The above shows information about all known Vagrant environments on this machine. This data is cached and may not be completely @@ -371,17 +367,6 @@ en: Machine name: %{name} Active provider: %{active_provider} Requested provider: %{requested_provider} - ansible_failed: |- - Ansible failed to complete successfully. Any error output should be - visible above. Please fix these errors and try again. - ansible_playbook_app_not_found: |- - The "ansible-playbook" program could not be found! Please verify - that "ansible-playbook" is available on the PATH of your host - system, and try again. - - If you haven't installed Ansible yet, please install Ansible - on your system. Vagrant can't do this for you in a safe, automated - way. Please see ansible.cc for more info. batch_multi_error: |- An error occurred while executing multiple actions in parallel. Any errors that occurred are shown below. @@ -614,6 +599,14 @@ en: issues. The error from Bundler is: %{message} + cant_read_mac_addresses: |- + The provider you are using ('%{provider}') doesn't support the + "nic_mac_addresses" provider capability which is required + for advanced networking to work with this guest OS. Please inform + the author of the provider to add this feature. + + Until then, you must remove any networking configurations other + than forwarded ports from your Vagrantfile for Vagrant to continue. capability_host_explicit_not_detected: |- The explicit capability host specified of '%{value}' could not be found. @@ -739,6 +732,13 @@ en: downloader_interrupted: |- The download was interrupted by an external signal. It did not complete. + env_inval: |- + Vagrant received an "EINVAL" error while attempting to set some + environment variables. This is usually caused by the total size of your + environment variables being too large. Vagrant sets a handful of + environment variables to function and requires this to work. Please + delete some environment variables prior to executing Vagrant to + fix this. environment_locked: |- Vagrant attempted to acquire a lock named '%{name}', but this lock is being held by another instance of Vagrant already. Please @@ -826,10 +826,11 @@ en: that the NFS client software is properly installed, and consult any resources specific to the linux distro you're using for more information on how to do this. - linux_rdesktop_not_found: |- - The `rdesktop` application was not found. Vagrant requires this - in order to connect via RDP to the Vagrant environment. Please ensure - this application is installed and available on the path and try again. + linux_rdp_client_not_found: |- + An appropriate RDP client was not found. Vagrant requires either + `xfreerdp` or `rdesktop` in order to connect via RDP to the Vagrant + environment. Please ensure one of these applications is installed and + available on the path and try again. machine_action_locked: |- An action '%{action}' was attempted on the machine '%{name}', but another process is already executing an action on the machine. @@ -1014,6 +1015,13 @@ en: This is a limitation of this provider. Please report this as a feature request to the provider in question. To install this provider, you'll have to do so manually. + provider_checksum_mismatch: |- + The checksum of the downloaded provider '%{provider}' did not match the + expected value. If the problem persists, please install the provider + manually. + + Expected: %{expected} + Received: %{actual} provider_install_failed: |- Installation of the provider '%{provider}' failed! The stdout and stderr are shown below. Please read the error output, resolve it, @@ -1314,10 +1322,14 @@ en: This is an internal error that should be reported as a bug. virtualbox_invalid_version: |- Vagrant has detected that you have a version of VirtualBox installed - that is not supported. Please install one of the supported versions - listed below to use Vagrant: + that is not supported by this version of Vagrant. Please install one of + the supported versions listed below to use Vagrant: %{supported_versions} + + A Vagrant update may also be available that adds support for the version + you specified. Please check www.vagrantup.com/downloads.html to download + the latest version. virtualbox_kernel_module_not_loaded: |- VirtualBox is complaining that the kernel module is not loaded. Please run `VBoxManage --version` or open the VirtualBox GUI to see the error @@ -1404,7 +1416,10 @@ en: common: bad_field: "The following settings shouldn't exist: %{fields}" chef: - cookbooks_path_empty: "Must specify a cookbooks path for chef solo." + cookbooks_path_empty: |- + Missing required value for `chef.cookbooks_path'. + nodes_path_empty: |- + Missing required value for `chef.nodes_path'. environment_path_missing: |- Environment path not found: %{path} environment_path_required: |- @@ -1723,7 +1738,7 @@ en: Fixed port collision for %{guest_port} => %{host_port}. Now on port %{new_port}. forwarding: Forwarding ports... forwarding_entry: |- - %{guest_port} => %{host_port} (adapter %{adapter}) + %{guest_port} (guest) => %{host_port} (host) (adapter %{adapter}) non_nat: |- VirtualBox adapter #%{adapter} not configured as "NAT". Skipping port forwards on this adapter. @@ -1933,6 +1948,8 @@ en: "The cookbook path '%{path}' doesn't exist. Ignoring..." json: "Generating chef JSON and uploading..." client_key_folder: "Creating folder to hold client key..." + generating_node_name: |- + Auto-generating node name for Chef... 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. @@ -1986,6 +2003,12 @@ en: upload_path_empty: |- The Chef Apply provisioner requires that the `config.chef.upload_path` be set to a non-nil, non-empty value. + missing_node_name: |- + The Chef Client provisioner cannot delete the %{deletable} from + the Chef server because Vagrant does not know the `node_name'! You + need to manually delete the %{deletable} from the Chef server. You + can specify the `node_name' in the Chef configuration to prevent this + in the future. deleting_from_server: "Deleting %{deletable} \"%{name}\" from Chef server..." file: @@ -2042,19 +2065,78 @@ en: invalid_encoding: |- Invalid encoding '%{actual}' for script at '%{path}'. Must be '%{default}' or UTF-8. + env_must_be_a_hash: |- + The shell provisioner's `env' option must be a hash. no_path_or_inline: "One of `path` or `inline` must be set." 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." + interactive_not_elevated: "To be interactive, it must also be privileged." ansible: - no_playbook: "`playbook` must be set for the Ansible provisioner." - playbook_path_invalid: "`playbook` for the Ansible provisioner does not exist on the host system: %{path}" - inventory_path_invalid: "`inventory_path` for the Ansible provisioner does not exist on the host system: %{path}" - vault_password_file_invalid: "`vault_password_file` for the Ansible provisioner does not exist on the host system: %{path}" - extra_vars_invalid: "`extra_vars` for the Ansible provisioner must be a hash or a path to an existing file. Received: %{value} (as %{type})" + ansible_host_pattern_detected: |- + Vagrant has detected a host range pattern in the `groups` option. + Vagrant doesn't fully check the validity of these parameters! + + Please check https://docs.ansible.com/ansible/intro_inventory.html#hosts-and-groups + for more information. + cannot_detect: |- + Vagrant does not support detecting whether Ansible is installed + for the guest OS running in the machine. Vagrant will assume it is + installed and attempt to continue. + + If you'd like this provisioner to be improved, please + take a look at the Vagrant source code linked below and try + to contribute back support. Thank you! + + https://github.com/mitchellh/vagrant + errors: + ansible_command_failed: |- + Ansible failed to complete successfully. Any error output should be + visible above. Please fix these errors and try again. + ansible_not_found_on_guest: |- + The Ansible software could not be found! Please verify + that Ansible is correctly installed on your guest system. + + If you haven't installed Ansible yet, please install Ansible + on your Vagrant basebox, or enable the automated setup with the + `install` option of this provisioner. Please check + https://docs.vagrantup.com/v2/provisioning/ansible_local.html + for more information. + ansible_not_found_on_host: |- + The Ansible software could not be found! Please verify + that Ansible is correctly installed on your host system. + + If you haven't installed Ansible yet, please install Ansible + on your host system. Vagrant can't do this for you in a safe and + automated way. + Please check https://docs.ansible.com for more information. + ansible_version_not_found_on_guest: |- + The requested Ansible version (%{required_version}) was not found on the guest. + Please check the ansible installation on your guest system, + or adapt the `version` option of this provisioner in your Vagrantfile. + See https://docs.vagrantup.com/v2/provisioning/ansible_local.html + for more information. + extra_vars_invalid: |- + `extra_vars` must be a hash or a path to an existing file. Received: %{value} (as %{type}) + galaxy_role_file_invalid: |- + `galaxy_role_file` does not exist on the %{system}: %{path} + inventory_path_invalid: |- + `inventory_path` does not exist on the %{system}: %{path} + no_playbook: |- + `playbook` file path must be set. + playbook_path_invalid: |- + `playbook` does not exist on the %{system}: %{path} + vault_password_file_invalid: |- + `vault_password_file` does not exist on the %{system}: %{path} + installing: "Installing Ansible..." + running_galaxy: "Running ansible-galaxy..." + running_playbook: "Running ansible-playbook..." + windows_not_supported_for_control_machine: |- + Windows is not officially supported for the Ansible Control Machine. + Please check https://docs.ansible.com/intro_installation.html#control-machine-requirements docker: not_running: "Docker is not running on the guest VM." diff --git a/templates/locales/guest_windows.yml b/templates/locales/guest_windows.yml index a648f5a09..f9a3bf997 100644 --- a/templates/locales/guest_windows.yml +++ b/templates/locales/guest_windows.yml @@ -1,14 +1,6 @@ en: vagrant_windows: errors: - cant_read_mac_addresses: |- - The provider being used to start Windows ('%{provider}') - doesn't support the "nic_mac_addresses" capability which is required - for advanced networking to work with Windows guests. Please inform - the author of the provider to add this feature. - - Until then, you must remove any networking configurations other - than forwarded ports from your Vagrantfile for Vagrant to continue. network_winrm_required: |- Configuring networks on Windows requires the communicator to be set to WinRM. To do this, add the following to your Vagrantfile: diff --git a/templates/provisioners/chef_solo/solo.erb b/templates/provisioners/chef_solo/solo.erb index 25d3346b7..7bfc32717 100644 --- a/templates/provisioners/chef_solo/solo.erb +++ b/templates/provisioners/chef_solo/solo.erb @@ -8,12 +8,12 @@ cookbook_path <%= cookbooks_path.inspect %> role_path <%= roles_path.size == 1 ? roles_path.first.inspect : roles_path.inspect %> <% end %> log_level <%= log_level.inspect %> -verbose_logging <%= verbose_logging.inspect %> +verbose_logging <%= verbose_logging.inspect %> encrypted_data_bag_secret <%= encrypted_data_bag_secret.inspect %> <% if data_bags_path -%> -data_bag_path <%= data_bags_path.inspect %> +data_bag_path <%= data_bags_path.size == 1 ? data_bags_path.first.inspect : data_bags_path.inspect %> <% end %> <% if recipe_url -%> @@ -31,8 +31,8 @@ environment "<%= environment %>" <% if local_mode -%> local_mode true <% end -%> -<% if node_path -%> -node_path <%= node_path.inspect %> +<% if nodes_path -%> +node_path <%= nodes_path.inspect %> <% end -%> http_proxy <%= http_proxy.inspect %> diff --git a/templates/provisioners/chef_zero/zero.erb b/templates/provisioners/chef_zero/zero.erb index 29de30d23..fa533152b 100644 --- a/templates/provisioners/chef_zero/zero.erb +++ b/templates/provisioners/chef_zero/zero.erb @@ -9,6 +9,7 @@ role_path <%= roles_path.size == 1 ? roles_path.first.inspect : roles_path.inspe <% end %> log_level <%= log_level.inspect %> verbose_logging <%= verbose_logging.inspect %> + <% if !enable_reporting %> enable_reporting <%= enable_reporting.inspect %> <% end %> @@ -16,7 +17,7 @@ enable_reporting <%= enable_reporting.inspect %> encrypted_data_bag_secret <%= encrypted_data_bag_secret.inspect %> <% if data_bags_path -%> -data_bag_path <%= data_bags_path.inspect %> +data_bag_path <%= data_bags_path.size == 1 ? data_bags_path.first.inspect : data_bags_path.inspect %> <% end %> <% if environments_path %> @@ -31,8 +32,8 @@ environment "<%= environment %>" chef_zero.enabled true local_mode true <% end -%> -<% if node_path -%> -node_path <%= node_path.inspect %> +<% if nodes_path -%> +node_path <%= nodes_path.inspect %> <% end -%> <% if formatter %> diff --git a/test/acceptance/provider-virtualbox/linked_clone_spec.rb b/test/acceptance/provider-virtualbox/linked_clone_spec.rb new file mode 100644 index 000000000..22785f1c7 --- /dev/null +++ b/test/acceptance/provider-virtualbox/linked_clone_spec.rb @@ -0,0 +1,35 @@ +# This tests that VM is up as a linked clone +shared_examples 'provider/linked_clone' do |provider, options| + if !options[:box] + raise ArgumentError, + "box option must be specified for provider: #{provider}" + end + + include_context 'acceptance' + + before do + environment.skeleton('linked_clone') + assert_execute('vagrant', 'box', 'add', 'basic', options[:box]) + end + + after do + assert_execute('vagrant', 'destroy', '--force') + end + + it 'creates machine as linked clone' do + status('Test: machine is created successfully') + result = execute('vagrant', 'up', "--provider=#{provider}") + expect(result).to exit_with(0) + + status('Test: master VM is created') + expect(result.stdout).to match(/master VM/) + + status('Test: machine is a master VM clone') + expect(result.stdout).to match(/Cloning/) + + status('Test: machine is available by ssh') + result = execute('vagrant', 'ssh', '-c', 'echo foo') + expect(result).to exit_with(0) + expect(result.stdout).to match(/foo\n$/) + end +end diff --git a/test/acceptance/skeletons/linked_clone/Vagrantfile b/test/acceptance/skeletons/linked_clone/Vagrantfile new file mode 100644 index 000000000..4562b9cf9 --- /dev/null +++ b/test/acceptance/skeletons/linked_clone/Vagrantfile @@ -0,0 +1,7 @@ +Vagrant.configure('2') do |config| + config.vm.box = 'basic' + + config.vm.provider 'virtualbox' do |v| + v.linked_clone = true + end +end diff --git a/test/support/isolated_environment.rb b/test/support/isolated_environment.rb index d9a49feee..719966bb2 100644 --- a/test/support/isolated_environment.rb +++ b/test/support/isolated_environment.rb @@ -4,6 +4,8 @@ require "tmpdir" require "log4r" +require "vagrant/util/platform" + # This class manages an isolated environment for Vagrant to # run in. It creates a temporary directory to act as the # working directory as well as sets a custom home directory. @@ -26,7 +28,7 @@ class IsolatedEnvironment @logger = Log4r::Logger.new("test::isolated_environment") # Create a temporary directory for our work - @tempdir = Dir.mktmpdir("vagrant") + @tempdir = Vagrant::Util::Platform.fs_real_path(Dir.mktmpdir("vagrant")) @logger.info("Initialize isolated environment: #{@tempdir}") # Setup the home and working directories diff --git a/test/unit/plugins/commands/box/command/update_test.rb b/test/unit/plugins/commands/box/command/update_test.rb index 4477d0aa5..067d0dd35 100644 --- a/test/unit/plugins/commands/box/command/update_test.rb +++ b/test/unit/plugins/commands/box/command/update_test.rb @@ -19,6 +19,11 @@ describe VagrantPlugins::CommandBox::Command::Update do let(:action_runner) { double("action_runner") } let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) } + let(:download_options) { ["--insecure", + "--cacert", "foo", + "--capath", "bar", + "--cert", "baz"] } + subject { described_class.new(argv, iso_env) } before do @@ -86,6 +91,10 @@ describe VagrantPlugins::CommandBox::Command::Update do expect(opts[:box_url]).to eq(metadata_url.to_s) expect(opts[:box_provider]).to eq("virtualbox") expect(opts[:box_version]).to eq("1.1") + expect(opts[:box_download_ca_path]).to be_nil + expect(opts[:box_download_ca_cert]).to be_nil + expect(opts[:box_client_cert]).to be_nil + expect(opts[:box_download_insecure]).to be_nil end opts @@ -158,6 +167,50 @@ describe VagrantPlugins::CommandBox::Command::Update do end end + context "download options are specified" do + let(:argv) { ["--box", "foo" ].concat(download_options) } + + it "passes down download options" do + metadata_url.open("w") do |f| + f.write(<<-RAW) + { + "name": "foo", + "versions": [ + { + "version": "1.0" + }, + { + "version": "1.1", + "providers": [ + { + "name": "virtualbox", + "url": "bar" + } + ] + } + ] + } + RAW + end + + action_called = false + allow(action_runner).to receive(:run) do |action, opts| + if opts[:box_provider] + action_called = true + expect(opts[:box_download_ca_cert]).to eq("foo") + expect(opts[:box_download_ca_path]).to eq("bar") + expect(opts[:box_client_cert]).to eq("baz") + expect(opts[:box_download_insecure]).to be_true + end + + opts + end + + subject.execute + expect(action_called).to be_true + end + end + context "with a box that doesn't exist" do let(:argv) { ["--box", "nope"] } @@ -192,7 +245,10 @@ describe VagrantPlugins::CommandBox::Command::Update do it "doesn't update boxes if they're up-to-date" do machine.stub(box: box) expect(box).to receive(:has_update?). - with(machine.config.vm.box_version). + with(machine.config.vm.box_version, + {download_options: + {ca_cert: nil, ca_path: nil, client_cert: nil, + insecure: false}}). and_return(nil) expect(action_runner).to receive(:run).never @@ -200,41 +256,101 @@ describe VagrantPlugins::CommandBox::Command::Update do subject.execute end - it "updates boxes if they have an update" do - md = Vagrant::BoxMetadata.new(StringIO.new(<<-RAW)) - { - "name": "foo", - "versions": [ - { - "version": "1.0" - }, - { - "version": "1.1", - "providers": [ - { - "name": "virtualbox", - "url": "bar" - } - ] - } - ] - } - RAW - - machine.stub(box: box) - expect(box).to receive(:has_update?). - with(machine.config.vm.box_version). - and_return([md, md.version("1.1"), md.version("1.1").provider("virtualbox")]) - - expect(action_runner).to receive(:run).with { |action, opts| - expect(opts[:box_url]).to eq(box.metadata_url) - expect(opts[:box_provider]).to eq("virtualbox") - expect(opts[:box_version]).to eq("1.1") - expect(opts[:ui]).to equal(machine.ui) - true + context "boxes have an update" do + let(:md) { + md = Vagrant::BoxMetadata.new(StringIO.new(<<-RAW)) + { + "name": "foo", + "versions": [ + { + "version": "1.0" + }, + { + "version": "1.1", + "providers": [ + { + "name": "virtualbox", + "url": "bar" + } + ] + } + ] + } + RAW } - subject.execute + before { machine.stub(box: box) } + + it "updates boxes" do + expect(box).to receive(:has_update?). + with(machine.config.vm.box_version, + {download_options: + {ca_cert: nil, ca_path: nil, client_cert: nil, + insecure: false}}). + and_return([md, md.version("1.1"), md.version("1.1").provider("virtualbox")]) + + expect(action_runner).to receive(:run).with { |action, opts| + expect(opts[:box_url]).to eq(box.metadata_url) + expect(opts[:box_provider]).to eq("virtualbox") + expect(opts[:box_version]).to eq("1.1") + expect(opts[:ui]).to equal(machine.ui) + true + } + + subject.execute + end + + context "machine has download options" do + before do + machine.config.vm.box_download_ca_cert = "oof" + machine.config.vm.box_download_ca_path = "rab" + machine.config.vm.box_download_client_cert = "zab" + machine.config.vm.box_download_insecure = false + end + + it "uses download options from machine" do + expect(box).to receive(:has_update?). + with(machine.config.vm.box_version, + {download_options: + {ca_cert: "oof", ca_path: "rab", client_cert: "zab", + insecure: false}}). + and_return([md, md.version("1.1"), md.version("1.1").provider("virtualbox")]) + + expect(action_runner).to receive(:run).with { |action, opts| + expect(opts[:box_download_ca_cert]).to eq("oof") + expect(opts[:box_download_ca_path]).to eq("rab") + expect(opts[:box_client_cert]).to eq("zab") + expect(opts[:box_download_insecure]).to be_false + true + } + + subject.execute + end + + context "download options are specified on the command line" do + let(:argv) { download_options } + + it "overrides download options from machine with options from CLI" do + expect(box).to receive(:has_update?). + with(machine.config.vm.box_version, + {download_options: + {ca_cert: "foo", ca_path: "bar", client_cert: "baz", + insecure: true}}). + and_return([md, md.version("1.1"), + md.version("1.1").provider("virtualbox")]) + + expect(action_runner).to receive(:run).with { |action, opts| + expect(opts[:box_download_ca_cert]).to eq("foo") + expect(opts[:box_download_ca_path]).to eq("bar") + expect(opts[:box_client_cert]).to eq("baz") + expect(opts[:box_download_insecure]).to be_true + true + } + + subject.execute + end + end + end end end end diff --git a/test/unit/plugins/commands/login/client_test.rb b/test/unit/plugins/commands/login/client_test.rb index f57670eb0..2f49ec6f5 100644 --- a/test/unit/plugins/commands/login/client_test.rb +++ b/test/unit/plugins/commands/login/client_test.rb @@ -67,7 +67,10 @@ describe VagrantPlugins::LoginCommand::Client do "token" => "baz", } - headers = { "Content-Type" => "application/json" } + headers = { + "Accept" => "application/json", + "Content-Type" => "application/json", + } stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). with(body: JSON.dump(request), headers: headers). diff --git a/test/unit/plugins/commands/login/middleware/add_authentication_test.rb b/test/unit/plugins/commands/login/middleware/add_authentication_test.rb index 99c99a8af..ee4b2ab7d 100644 --- a/test/unit/plugins/commands/login/middleware/add_authentication_test.rb +++ b/test/unit/plugins/commands/login/middleware/add_authentication_test.rb @@ -84,5 +84,21 @@ describe VagrantPlugins::LoginCommand::AddAuthentication do expect(env[:box_urls]).to eq(expected) end + + it "does not append multiple access_tokens" do + token = "foobarbaz" + VagrantPlugins::LoginCommand::Client.new(iso_env).store_token(token) + + original = [ + "#{server_url}/foo.box?access_token=existing", + "#{server_url}/bar.box?arg=true", + ] + + env[:box_urls] = original.dup + subject.call(env) + + expect(env[:box_urls][0]).to eq("#{server_url}/foo.box?access_token=existing") + expect(env[:box_urls][1]).to eq("#{server_url}/bar.box?arg=true&access_token=foobarbaz") + end end end diff --git a/test/unit/plugins/commands/port/command_test.rb b/test/unit/plugins/commands/port/command_test.rb new file mode 100644 index 000000000..23abf5d7b --- /dev/null +++ b/test/unit/plugins/commands/port/command_test.rb @@ -0,0 +1,137 @@ +require File.expand_path("../../../../base", __FILE__) + +require Vagrant.source_root.join("plugins/commands/port/command") + +describe VagrantPlugins::CommandPort::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(:state) { double(:state, id: :running) } + + let(:machine) { env.machine(env.machine_names[0], :dummy) } + + before(:all) do + I18n.load_path << Vagrant.source_root.join("plugins/commands/port/locales/en.yml") + I18n.reload! + end + + subject { described_class.new([], env) } + + before do + allow(machine).to receive(:state).and_return(state) + allow(subject).to receive(:with_target_vms) { |&block| block.call(machine) } + end + + describe "#execute" do + 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([], iso_env.create_vagrant_env) + + expect { subject.execute }.to raise_error(Vagrant::Errors::ConfigInvalid) { |err| + expect(err.message).to include("The following settings shouldn't exist: bad") + } + end + + it "ensures the vm is running" do + allow(state).to receive(:id).and_return(:stopped) + expect(env.ui).to receive(:error).with { |message, _| + expect(message).to include("does not support listing forwarded ports") + } + + expect(subject.execute).to eq(1) + end + + it "shows a friendly error when the capability is not supported" do + allow(machine.provider).to receive(:capability?).and_return(false) + expect(env.ui).to receive(:error).with { |message, _| + expect(message).to include("does not support listing forwarded ports") + } + + expect(subject.execute).to eq(1) + end + + it "returns a friendly message when there are no forwarded ports" do + allow(machine.provider).to receive(:capability?).and_return(true) + allow(machine.provider).to receive(:capability).with(:forwarded_ports) + .and_return([]) + + expect(env.ui).to receive(:info).with { |message, _| + expect(message).to include("there are no forwarded ports") + } + + expect(subject.execute).to eq(0) + end + + it "returns the list of ports" do + allow(machine.provider).to receive(:capability?).and_return(true) + allow(machine.provider).to receive(:capability).with(:forwarded_ports) + .and_return([[2222,22], [1111,11]]) + + output = "" + allow(env.ui).to receive(:info) do |data| + output << data + end + + expect(subject.execute).to eq(0) + + expect(output).to include("forwarded ports for the machine") + expect(output).to include("22 (guest) => 2222 (host)") + expect(output).to include("11 (guest) => 1111 (host)") + end + + it "prints the matching host port when --guest is given" do + argv = ["--guest", "22"] + subject = described_class.new(argv, env) + + allow(machine.provider).to receive(:capability?).and_return(true) + allow(machine.provider).to receive(:capability).with(:forwarded_ports) + .and_return([[2222,22]]) + + output = "" + allow(env.ui).to receive(:info) do |data| + output << data + end + + expect(subject.execute).to eq(0) + + expect(output).to eq("2222") + end + + it "returns an error with no port is mapped to the --guest option" do + argv = ["--guest", "80"] + subject = described_class.new(argv, env) + + allow(machine.provider).to receive(:capability?).and_return(true) + allow(machine.provider).to receive(:capability).with(:forwarded_ports) + .and_return([[2222,22]]) + + output = "" + allow(env.ui).to receive(:error) do |data| + output << data + end + + expect(subject.execute).to_not eq(0) + + expect(output).to include("not currently mapping port 80") + end + end +end diff --git a/test/unit/plugins/commands/ssh_config/command_test.rb b/test/unit/plugins/commands/ssh_config/command_test.rb index e458776ec..0922c9a7a 100644 --- a/test/unit/plugins/commands/ssh_config/command_test.rb +++ b/test/unit/plugins/commands/ssh_config/command_test.rb @@ -92,8 +92,8 @@ Host #{machine.name} subject.execute - expect(output).to include("IdentityFile foo") - expect(output).to include("IdentityFile bar") + expect(output).to include('IdentityFile "foo"') + expect(output).to include('IdentityFile "bar"') end it "puts quotes around an identityfile path if it has a space" do @@ -107,5 +107,17 @@ Host #{machine.name} expect(output).to include('IdentityFile "with a space"') end + + it "escapes special characters" do + allow(machine).to receive(:ssh_info) { ssh_info.merge(private_key_path: ['/private/tmp/test of "vagrant" plugin/']) } + output = "" + allow(subject).to receive(:safe_puts) do |data| + output += data if data + end + + subject.execute + + expect(output).to include('"/private/tmp/test of \"vagrant\" plugin/"') + end end end diff --git a/test/unit/plugins/communicators/winrm/communicator_test.rb b/test/unit/plugins/communicators/winrm/communicator_test.rb index e98f646b3..3e21242d9 100644 --- a/test/unit/plugins/communicators/winrm/communicator_test.rb +++ b/test/unit/plugins/communicators/winrm/communicator_test.rb @@ -21,6 +21,7 @@ describe VagrantPlugins::CommunicatorWinRM::Communicator do before do allow(shell).to receive(:username).and_return('vagrant') allow(shell).to receive(:password).and_return('password') + allow(shell).to receive(:execution_time_limit).and_return('PT2H') end describe ".wait_for_ready" do @@ -87,12 +88,21 @@ 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\" " + - "-username \"vagrant\" -password \"password\" -encoded_command \"ZABpAHIAOwAgAGUAeABpAHQAIAAkAEwAQQBTAFQARQBYAEkAVABDAE8ARABFAA==\"") + expect(cmd).to eq("powershell -executionpolicy bypass -file 'c:/tmp/vagrant-elevated-shell.ps1' " + + "-username 'vagrant' -password 'password' -encoded_command 'JABQAHIAbwBnAHIAZQBzAHMAUAByAGUAZgBlAHIAZQBuAGMAZQA9ACcAUwBpAGwAZQBuAHQAbAB5AEMAbwBuAHQAaQBuAHUAZQAnADsAIABkAGkAcgA7ACAAZQB4AGkAdAAgACQATABBAFMAVABFAFgASQBUAEMATwBEAEUA' -execution_time_limit 'PT2H'") end.and_return({ exitcode: 0 }) expect(subject.execute("dir", { elevated: true })).to eq(0) end + it "wraps command in elevated and interactive shell script when elevated and interactive are 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' " + + "-username 'vagrant' -password 'password' -encoded_command 'JABQAHIAbwBnAHIAZQBzAHMAUAByAGUAZgBlAHIAZQBuAGMAZQA9ACcAUwBpAGwAZQBuAHQAbAB5AEMAbwBuAHQAaQBuAHUAZQAnADsAIABkAGkAcgA7ACAAZQB4AGkAdAAgACQATABBAFMAVABFAFgASQBUAEMATwBEAEUA' -execution_time_limit 'PT2H'") + end.and_return({ exitcode: 0 }) + expect(subject.execute("dir", { elevated: true, interactive: true })).to eq(0) + end + it "can use cmd shell" do expect(shell).to receive(:cmd).with(kind_of(String)).and_return({ exitcode: 0 }) expect(subject.execute("dir", { shell: :cmd })).to eq(0) 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 index 047d05e82..714e633d8 100644 --- a/test/unit/plugins/guests/linux/cap/mount_shared_folder_test.rb +++ b/test/unit/plugins/guests/linux/cap/mount_shared_folder_test.rb @@ -10,35 +10,4 @@ describe "VagrantPlugins::GuestLinux::Cap::MountSharedFolder" do 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/kernel_v2/config/vm_test.rb b/test/unit/plugins/kernel_v2/config/vm_test.rb index 146b3f344..992c27716 100644 --- a/test/unit/plugins/kernel_v2/config/vm_test.rb +++ b/test/unit/plugins/kernel_v2/config/vm_test.rb @@ -204,25 +204,25 @@ describe VagrantPlugins::Kernel_V2::VMConfig do it "allows overriding WinRM" do subject.communicator = :winrm subject.network "forwarded_port", - guest: 22, host: 14100, id: "winrm" + guest: 5985, host: 14100, id: "winrm" subject.finalize! winrm_network = find_network 'winrm' - expect(winrm_network[:guest]).to eq(22) + expect(winrm_network[:guest]).to eq(5985) 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.communicator = :winrm subject.network "forwarded_port", - guest: 22, host: 14100, id: "winrmssl" + guest: 5986, host: 14100, id: "winrm-ssl" subject.finalize! - winrmssl_network = find_network 'winrmssl' - expect(winrmssl_network[:guest]).to eq(22) + winrmssl_network = find_network 'winrm-ssl' + expect(winrmssl_network[:guest]).to eq(5986) expect(winrmssl_network[:host]).to eq(14100) - expect(winrmssl_network[:id]).to eq("winrmssl") + expect(winrmssl_network[:id]).to eq("winrm-ssl") end it "turns all forwarded port ports to ints" do diff --git a/test/unit/plugins/providers/hyperv/provider_test.rb b/test/unit/plugins/providers/hyperv/provider_test.rb index 0613751d1..2a895706d 100644 --- a/test/unit/plugins/providers/hyperv/provider_test.rb +++ b/test/unit/plugins/providers/hyperv/provider_test.rb @@ -15,6 +15,7 @@ describe VagrantPlugins::HyperV::Provider do machine.stub(id: "foo") platform.stub(windows?: true) platform.stub(windows_admin?: true) + platform.stub(windows_hyperv_admin?: true) powershell.stub(available?: true) end @@ -26,11 +27,18 @@ describe VagrantPlugins::HyperV::Provider do expect(subject).to_not be_usable end - it "returns false if not an admin" do + it "returns false if neither an admin nor a hyper-v admin" do platform.stub(windows_admin?: false) + platform.stub(windows_hyperv_admin?: false) expect(subject).to_not be_usable end + it "returns true if not an admin but is a hyper-v admin" do + platform.stub(windows_admin?: false) + platform.stub(windows_hyperv_admin?: true) + expect(subject).to be_usable + end + it "returns false if powershell is not available" do powershell.stub(available?: false) expect(subject).to_not be_usable @@ -43,8 +51,17 @@ describe VagrantPlugins::HyperV::Provider do to raise_error(VagrantPlugins::HyperV::Errors::WindowsRequired) end - it "raises an exception if not an admin" do + it "raises an exception if neither an admin nor a hyper-v admin" do platform.stub(windows_admin?: false) + platform.stub(windows_hyperv_admin?: false) + + expect { subject.usable?(true) }. + to raise_error(VagrantPlugins::HyperV::Errors::AdminRequired) + end + + it "raises an exception if neither an admin nor a hyper-v admin" do + platform.stub(windows_admin?: false) + platform.stub(windows_hyperv_admin?: false) expect { subject.usable?(true) }. to raise_error(VagrantPlugins::HyperV::Errors::AdminRequired) diff --git a/test/unit/plugins/providers/virtualbox/action/network_fix_ipv6_test.rb b/test/unit/plugins/providers/virtualbox/action/network_fix_ipv6_test.rb new file mode 100644 index 000000000..2f99b954b --- /dev/null +++ b/test/unit/plugins/providers/virtualbox/action/network_fix_ipv6_test.rb @@ -0,0 +1,165 @@ +require_relative "../base" +require 'socket' + +describe VagrantPlugins::ProviderVirtualBox::Action::NetworkFixIPv6 do + include_context "unit" + include_context "virtualbox" + + let(:iso_env) do + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env + end + + let(:machine) do + iso_env.machine(iso_env.machine_names[0], :dummy).tap do |m| + m.provider.stub(driver: driver) + end + end + + let(:env) {{ machine: machine }} + let(:app) { lambda { |*args| }} + let(:driver) { double("driver") } + + subject { described_class.new(app, env) } + + it "ignores nil IP addresses" do + allow(machine.config.vm).to receive(:networks) + .and_return(private_network: { ip: nil }) + expect { subject.call(env) }.to_not raise_error + end + + it "blank nil IP addresses" do + allow(machine.config.vm).to receive(:networks) + .and_return(private_network: { ip: "" }) + expect { subject.call(env) }.to_not raise_error + end + + context "with IPv6 interfaces" do + let(:socket) { double("socket") } + + before do + # This address is only used to trigger the fixup code. It doesn't matter + # what it is. + allow(machine.config.vm).to receive(:networks) + .and_return(private_network: { ip: 'fe:80::' }) + allow(UDPSocket).to receive(:new).with(Socket::AF_INET6) + .and_return(socket) + socket.stub(:connect) + end + + it "only checks the interfaces associated with the VM" do + all_networks = [{name: "vboxnet0", + ipv6: "dead:beef::", + ipv6_prefix: 64, + status: 'Up' + }, + {name: "vboxnet1", + ipv6: "badd:badd::", + ipv6_prefix: 64, + status: 'Up' + } + ] + ifaces = { 1 => {type: :hostonly, hostonly: "vboxnet0"} + } + allow(machine.provider.driver).to receive(:read_network_interfaces) + .and_return(ifaces) + allow(machine.provider.driver).to receive(:read_host_only_interfaces) + .and_return(all_networks) + subject.call(env) + expect(socket).to have_received(:connect) + .with(all_networks[0][:ipv6] + (['ffff']*4).join(':'), 80) + end + + it "correctly uses the netmask to figure out the probe address" do + all_networks = [{name: "vboxnet0", + ipv6: "dead:beef::", + ipv6_prefix: 113, + status: 'Up' + } + ] + ifaces = { 1 => {type: :hostonly, hostonly: "vboxnet0"} + } + allow(machine.provider.driver).to receive(:read_network_interfaces) + .and_return(ifaces) + allow(machine.provider.driver).to receive(:read_host_only_interfaces) + .and_return(all_networks) + subject.call(env) + expect(socket).to have_received(:connect) + .with(all_networks[0][:ipv6] + '7fff', 80) + end + + it "should ignore interfaces that are down" do + all_networks = [{name: "vboxnet0", + ipv6: "dead:beef::", + ipv6_prefix: 64, + status: 'Down' + } + ] + ifaces = { 1 => {type: :hostonly, hostonly: "vboxnet0"} + } + allow(machine.provider.driver).to receive(:read_network_interfaces) + .and_return(ifaces) + allow(machine.provider.driver).to receive(:read_host_only_interfaces) + .and_return(all_networks) + subject.call(env) + expect(socket).to_not have_received(:connect) + end + + it "should ignore interfaces without an IPv6 address" do + all_networks = [{name: "vboxnet0", + ipv6: "", + ipv6_prefix: 0, + status: 'Up' + } + ] + ifaces = { 1 => {type: :hostonly, hostonly: "vboxnet0"} + } + allow(machine.provider.driver).to receive(:read_network_interfaces) + .and_return(ifaces) + allow(machine.provider.driver).to receive(:read_host_only_interfaces) + .and_return(all_networks) + subject.call(env) + expect(socket).to_not have_received(:connect) + end + + it "should ignore nat interfaces" do + all_networks = [{name: "vboxnet0", + ipv6: "", + ipv6_prefix: 0, + status: 'Up' + } + ] + ifaces = { 1 => {type: :nat} + } + allow(machine.provider.driver).to receive(:read_network_interfaces) + .and_return(ifaces) + allow(machine.provider.driver).to receive(:read_host_only_interfaces) + .and_return(all_networks) + subject.call(env) + expect(socket).to_not have_received(:connect) + end + + it "should reconfigure an interface if unreachable" do + all_networks = [{name: "vboxnet0", + ipv6: "dead:beef::", + ipv6_prefix: 64, + status: 'Up' + } + ] + ifaces = { 1 => {type: :hostonly, hostonly: "vboxnet0"} + } + allow(machine.provider.driver).to receive(:read_network_interfaces) + .and_return(ifaces) + allow(machine.provider.driver).to receive(:read_host_only_interfaces) + .and_return(all_networks) + allow(socket).to receive(:connect) + .with(all_networks[0][:ipv6] + (['ffff']*4).join(':'), 80) + .and_raise Errno::EHOSTUNREACH + allow(machine.provider.driver).to receive(:reconfig_host_only) + subject.call(env) + expect(machine.provider.driver).to have_received(:reconfig_host_only) + .with(all_networks[0]) + 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 index e9986492d..0d1f262e2 100644 --- a/test/unit/plugins/providers/virtualbox/action/network_test.rb +++ b/test/unit/plugins/providers/virtualbox/action/network_test.rb @@ -42,6 +42,34 @@ describe VagrantPlugins::ProviderVirtualBox::Action::Network do expect(called).to eq(true) end + it "creates a host-only interface with an IPv6 address :1" do + guest = double("guest") + machine.config.vm.network 'private_network', { type: :static, ip: 'dead:beef::100' } + #allow(driver).to receive(:read_bridged_interfaces) { [] } + allow(driver).to receive(:read_host_only_interfaces) { [] } + #allow(driver).to receive(:read_dhcp_servers) { [] } + allow(machine).to receive(:guest) { guest } + allow(driver).to receive(:create_host_only_network) {{ name: 'vboxnet0' }} + allow(guest).to receive(:capability) + interface_ip = 'dead:beef::1' + + subject.call(env) + + expect(driver).to have_received(:create_host_only_network).with({ + adapter_ip: interface_ip, + netmask: 64, + }) + + expect(guest).to have_received(:capability).with(:configure_networks, [{ + type: :static6, + adapter_ip: 'dead:beef::1', + ip: 'dead:beef::100', + netmask: 64, + auto_config: true, + interface: nil + }]) + end + context "with a dhcp private network" do let(:bridgedifs) { [] } let(:hostonlyifs) { [] } diff --git a/test/unit/plugins/providers/virtualbox/cap/public_address_test.rb b/test/unit/plugins/providers/virtualbox/cap/public_address_test.rb new file mode 100644 index 000000000..d9a581ed2 --- /dev/null +++ b/test/unit/plugins/providers/virtualbox/cap/public_address_test.rb @@ -0,0 +1,43 @@ +require_relative "../base" + +require Vagrant.source_root.join("plugins/providers/virtualbox/cap/public_address") + +describe VagrantPlugins::ProviderVirtualBox::Cap::PublicAddress do + include_context "unit" + + 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], :dummy).tap do |m| + allow(m).to receive(:state).and_return(state) + end + end + + let(:state) do + double(:state) + end + + describe "#public_address" do + it "returns nil when the machine is not running" do + allow(state).to receive(:id).and_return(:not_created) + expect(described_class.public_address(machine)).to be(nil) + end + + it "returns nil when there is no ssh info" do + allow(state).to receive(:id).and_return(:not_created) + allow(machine).to receive(:ssh_info).and_return(nil) + expect(described_class.public_address(machine)).to be(nil) + end + + it "returns the host" do + allow(state).to receive(:id).and_return(:running) + allow(machine).to receive(:ssh_info).and_return(host: "test") + expect(described_class.public_address(machine)).to eq("test") + end + end +end diff --git a/test/unit/plugins/providers/virtualbox/cap_test.rb b/test/unit/plugins/providers/virtualbox/cap_test.rb index baa7f610f..52eb9bc43 100644 --- a/test/unit/plugins/providers/virtualbox/cap_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap_test.rb @@ -15,14 +15,16 @@ describe VagrantPlugins::ProviderVirtualBox::Cap do let(:machine) do iso_env.machine(iso_env.machine_names[0], :dummy).tap do |m| m.provider.stub(driver: driver) + m.stub(state: state) end end let(:driver) { double("driver") } + let(:state) { double("state", id: :running) } describe "#forwarded_ports" do it "returns all the forwarded ports" do - expect(driver).to receive(:read_forwarded_ports).and_return([ + allow(driver).to receive(:read_forwarded_ports).and_return([ [nil, nil, 123, 456], [nil, nil, 245, 245], ]) @@ -32,5 +34,10 @@ describe VagrantPlugins::ProviderVirtualBox::Cap do 245 => 245, }) end + + it "returns nil when the machine is not running" do + allow(machine).to receive(:state).and_return(double(:state, id: :stopped)) + expect(described_class.forwarded_ports(machine)).to be(nil) + end end end diff --git a/test/unit/plugins/provisioners/ansible/config_test.rb b/test/unit/plugins/provisioners/ansible/config_test.rb index ab1b6dca0..f836382a3 100644 --- a/test/unit/plugins/provisioners/ansible/config_test.rb +++ b/test/unit/plugins/provisioners/ansible/config_test.rb @@ -1,25 +1,36 @@ require_relative "../../../base" require_relative "../support/shared/config" -require Vagrant.source_root.join("plugins/provisioners/ansible/config") +require "vagrant/util/platform" -describe VagrantPlugins::Ansible::Config do +require Vagrant.source_root.join("plugins/provisioners/ansible/config/host") + +describe VagrantPlugins::Ansible::Config::Host do include_context "unit" subject { described_class.new } let(:machine) { double("machine", env: Vagrant::Environment.new) } let(:existing_file) { File.expand_path(__FILE__) } - let(:non_existing_file) { "/this/does/not/exist" } + let(:non_existing_file) do + next "/this/does/not/exist" if !Vagrant::Util::Platform.windows? + "C:/foo/nope/nope" + end it "supports a list of options" do config_options = subject.public_methods(false).find_all { |i| i.to_s.end_with?('=') } config_options.map! { |i| i.to_s.sub('=', '') } + supported_options = %w( ask_sudo_pass ask_vault_pass extra_vars + force_remote_user + galaxy_command + galaxy_role_file + galaxy_roles_path groups host_key_checking + host_vars inventory_path limit playbook @@ -33,7 +44,7 @@ describe VagrantPlugins::Ansible::Config do vault_password_file verbose ) - expect(config_options.sort).to eql(supported_options) + expect(get_provisioner_option_names(described_class)).to eql(supported_options) end it "assigns default values to unset options" do @@ -41,22 +52,27 @@ describe VagrantPlugins::Ansible::Config do expect(subject.playbook).to be_nil expect(subject.extra_vars).to be_nil + expect(subject.force_remote_user).to be_true expect(subject.ask_sudo_pass).to be_false expect(subject.ask_vault_pass).to be_false expect(subject.vault_password_file).to be_nil expect(subject.limit).to be_nil expect(subject.sudo).to be_false expect(subject.sudo_user).to be_nil - expect(subject.verbose).to be_nil + expect(subject.verbose).to be_false expect(subject.tags).to be_nil expect(subject.skip_tags).to be_nil expect(subject.start_at_task).to be_nil + expect(subject.host_vars).to eq({}) expect(subject.groups).to eq({}) expect(subject.host_key_checking).to be_false expect(subject.raw_arguments).to be_nil expect(subject.raw_ssh_args).to be_nil end + describe "force_remote_user option" do + it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :force_remote_user, true + end describe "host_key_checking option" do it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :host_key_checking, false end @@ -79,7 +95,7 @@ describe VagrantPlugins::Ansible::Config do subject.finalize! result = subject.validate(machine) - expect(result["ansible provisioner"]).to eql([]) + expect(result["ansible remote provisioner"]).to eql([]) end it "returns an error if the playbook option is undefined" do @@ -87,8 +103,8 @@ describe VagrantPlugins::Ansible::Config do subject.finalize! result = subject.validate(machine) - expect(result["ansible provisioner"]).to eql([ - I18n.t("vagrant.provisioners.ansible.no_playbook") + expect(result["ansible remote provisioner"]).to eql([ + I18n.t("vagrant.provisioners.ansible.errors.no_playbook") ]) end @@ -97,9 +113,9 @@ describe VagrantPlugins::Ansible::Config do subject.finalize! result = subject.validate(machine) - expect(result["ansible provisioner"]).to eql([ - I18n.t("vagrant.provisioners.ansible.playbook_path_invalid", - path: non_existing_file) + expect(result["ansible remote provisioner"]).to eql([ + I18n.t("vagrant.provisioners.ansible.errors.playbook_path_invalid", + path: non_existing_file, system: "host") ]) end @@ -108,7 +124,7 @@ describe VagrantPlugins::Ansible::Config do subject.finalize! result = subject.validate(machine) - expect(result["ansible provisioner"]).to eql([]) + expect(result["ansible remote provisioner"]).to eql([]) end it "passes if the extra_vars option is a hash" do @@ -116,7 +132,7 @@ describe VagrantPlugins::Ansible::Config do subject.finalize! result = subject.validate(machine) - expect(result["ansible provisioner"]).to eql([]) + expect(result["ansible remote provisioner"]).to eql([]) end it "returns an error if the extra_vars option refers to a file that does not exist" do @@ -124,8 +140,8 @@ describe VagrantPlugins::Ansible::Config do subject.finalize! result = subject.validate(machine) - expect(result["ansible provisioner"]).to eql([ - I18n.t("vagrant.provisioners.ansible.extra_vars_invalid", + expect(result["ansible remote provisioner"]).to eql([ + I18n.t("vagrant.provisioners.ansible.errors.extra_vars_invalid", type: subject.extra_vars.class.to_s, value: subject.extra_vars.to_s) ]) @@ -136,8 +152,8 @@ describe VagrantPlugins::Ansible::Config do subject.finalize! result = subject.validate(machine) - expect(result["ansible provisioner"]).to eql([ - I18n.t("vagrant.provisioners.ansible.extra_vars_invalid", + expect(result["ansible remote provisioner"]).to eql([ + I18n.t("vagrant.provisioners.ansible.errors.extra_vars_invalid", type: subject.extra_vars.class.to_s, value: subject.extra_vars.to_s) ]) @@ -148,7 +164,7 @@ describe VagrantPlugins::Ansible::Config do subject.finalize! result = subject.validate(machine) - expect(result["ansible provisioner"]).to eql([]) + expect(result["ansible remote provisioner"]).to eql([]) end it "returns an error if inventory_path is specified, but does not exist" do @@ -156,9 +172,9 @@ describe VagrantPlugins::Ansible::Config do subject.finalize! result = subject.validate(machine) - expect(result["ansible provisioner"]).to eql([ - I18n.t("vagrant.provisioners.ansible.inventory_path_invalid", - path: non_existing_file) + expect(result["ansible remote provisioner"]).to eql([ + I18n.t("vagrant.provisioners.ansible.errors.inventory_path_invalid", + path: non_existing_file, system: "host") ]) end @@ -167,9 +183,20 @@ describe VagrantPlugins::Ansible::Config do subject.finalize! result = subject.validate(machine) - expect(result["ansible provisioner"]).to eql([ - I18n.t("vagrant.provisioners.ansible.vault_password_file_invalid", - path: non_existing_file) + expect(result["ansible remote provisioner"]).to eql([ + I18n.t("vagrant.provisioners.ansible.errors.vault_password_file_invalid", + path: non_existing_file, system: "host") + ]) + end + + it "returns an error if galaxy_role_file is specified, but does not exist" do + subject.galaxy_role_file = non_existing_file + subject.finalize! + + result = subject.validate(machine) + expect(result["ansible remote provisioner"]).to eql([ + I18n.t("vagrant.provisioners.ansible.errors.galaxy_role_file_invalid", + path: non_existing_file, system: "host") ]) end @@ -180,16 +207,16 @@ describe VagrantPlugins::Ansible::Config do subject.finalize! result = subject.validate(machine) - expect(result["ansible provisioner"]).to include( - I18n.t("vagrant.provisioners.ansible.playbook_path_invalid", - path: non_existing_file)) - expect(result["ansible provisioner"]).to include( - I18n.t("vagrant.provisioners.ansible.extra_vars_invalid", + expect(result["ansible remote provisioner"]).to include( + I18n.t("vagrant.provisioners.ansible.errors.playbook_path_invalid", + path: non_existing_file, system: "host")) + expect(result["ansible remote provisioner"]).to include( + I18n.t("vagrant.provisioners.ansible.errors.extra_vars_invalid", type: subject.extra_vars.class.to_s, value: subject.extra_vars.to_s)) - expect(result["ansible provisioner"]).to include( - I18n.t("vagrant.provisioners.ansible.inventory_path_invalid", - path: non_existing_file)) + expect(result["ansible remote provisioner"]).to include( + I18n.t("vagrant.provisioners.ansible.errors.inventory_path_invalid", + path: non_existing_file, system: "host")) end end diff --git a/test/unit/plugins/provisioners/ansible/provisioner_test.rb b/test/unit/plugins/provisioners/ansible/provisioner_test.rb index 24863adc1..31354a79c 100644 --- a/test/unit/plugins/provisioners/ansible/provisioner_test.rb +++ b/test/unit/plugins/provisioners/ansible/provisioner_test.rb @@ -1,7 +1,7 @@ require_relative "../../../base" -require Vagrant.source_root.join("plugins/provisioners/ansible/config") -require Vagrant.source_root.join("plugins/provisioners/ansible/provisioner") +require Vagrant.source_root.join("plugins/provisioners/ansible/config/host") +require Vagrant.source_root.join("plugins/provisioners/ansible/provisioner/host") # # Helper Functions @@ -15,7 +15,7 @@ def find_last_argument_after(ref_index, ansible_playbook_args, arg_pattern) return false end -describe VagrantPlugins::Ansible::Provisioner do +describe VagrantPlugins::Ansible::Provisioner::Host do include_context "unit" subject { described_class.new(machine, config) } @@ -37,7 +37,7 @@ VF end let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) } - let(:config) { VagrantPlugins::Ansible::Config.new } + let(:config) { VagrantPlugins::Ansible::Config::Host.new } let(:ssh_info) {{ private_key_path: ['/path/to/my/key'], username: 'testuser', @@ -67,15 +67,17 @@ VF # 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") + expected_args_count = 5, + 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("--user=#{machine.ssh_info[:username]}") - expect(args[2]).to eq("--connection=ssh") - expect(args[3]).to eq("--timeout=30") + expect(args[1]).to eq("--connection=ssh") + expect(args[2]).to eq("--timeout=30") inventory_count = args.count { |x| x =~ /^--inventory-file=.+$/ } expect(inventory_count).to be > 0 @@ -127,7 +129,7 @@ VF } end - it "enables '#{expected_transport_mode}' transport mode" do + it "enables '#{expected_transport_mode}' as default transport mode" do expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| index = args.rindex("--connection=#{expected_transport_mode}") expect(index).to be > 0 @@ -162,13 +164,17 @@ VF end end - def self.it_should_create_and_use_generated_inventory + def self.it_should_create_and_use_generated_inventory(with_ssh_user = true) it "generates an inventory with all active machines" do expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| 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]} ansible_ssh_private_key_file='#{machine.ssh_info[:private_key_path][0]}'\n") + if with_ssh_user + expect(inventory_content).to include("#{machine.name} ansible_ssh_host=#{machine.ssh_info[:host]} ansible_ssh_port=#{machine.ssh_info[:port]} ansible_ssh_user='#{machine.ssh_info[:username]}' ansible_ssh_private_key_file='#{machine.ssh_info[:private_key_path][0]}'\n") + else + expect(inventory_content).to include("#{machine.name} ansible_ssh_host=#{machine.ssh_info[:host]} ansible_ssh_port=#{machine.ssh_info[:port]} ansible_ssh_private_key_file='#{machine.ssh_info[:private_key_path][0]}'\n") + end 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 @@ -202,7 +208,7 @@ VF config.finalize! Vagrant::Util::Subprocess.stub(execute: Vagrant::Util::Subprocess::Result.new(1, "", "")) - expect {subject.provision}.to raise_error(Vagrant::Errors::AnsibleFailed) + expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleCommandFailed) end end @@ -215,58 +221,127 @@ VF inventory_content = File.read(generated_inventory_file) expect(inventory_content).to_not match(/^\s*\[^\\+\]\s*$/) - # 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(args.length).to be > 0 + # Ending this block with a negative expectation (to_not / not_to) + # would lead to a failure of the above expectation. + true } end - it "does not show the ansible-playbook command" do + it "doesn't show the ansible-playbook command" do expect(machine.env.ui).not_to receive(:detail).with { |full_command| expect(full_command).to include("ansible-playbook") } end end + describe "with host_vars option" do + it_should_create_and_use_generated_inventory + + it "adds host variables (given in Hash format) to the generated inventory" do + config.host_vars = { + machine1: {"http_port" => 80, "maxRequestsPerChild" => 808} + } + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + inventory_content = File.read(generated_inventory_file) + expect(inventory_content).to match("^" + Regexp.quote(machine.name) + ".+http_port=80 maxRequestsPerChild=808") + } + end + + it "adds host variables (given in Array format) to the generated inventory" do + config.host_vars = { + machine1: ["http_port=80", "maxRequestsPerChild=808"] + } + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + inventory_content = File.read(generated_inventory_file) + expect(inventory_content).to match("^" + Regexp.quote(machine.name) + ".+http_port=80 maxRequestsPerChild=808") + } + end + + it "adds host variables (given in String format) to the generated inventory " do + config.host_vars = { + :machine1 => "http_port=80 maxRequestsPerChild=808" + } + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + inventory_content = File.read(generated_inventory_file) + expect(inventory_content).to match("^" + Regexp.quote(machine.name) + ".+http_port=80 maxRequestsPerChild=808") + } + end + + it "retrieves the host variables by machine name, also in String format" do + config.host_vars = { + "machine1" => "http_port=80 maxRequestsPerChild=808" + } + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + inventory_content = File.read(generated_inventory_file) + expect(inventory_content).to match("^" + Regexp.quote(machine.name) + ".+http_port=80 maxRequestsPerChild=808") + } + end + end + describe "with groups option" do it_should_create_and_use_generated_inventory it "adds group sections to the generated inventory" do config.groups = { - "group1" => "#{machine.name}", - "group1:children" => 'bar', + "group1" => "machine1", + "group1:children" => 'bar group3', "group2" => [iso_env.machine_names[1]], "group3" => ["unknown", "#{machine.name}"], + "group4" => ["machine[1:2]", "machine[a:f]"], + "group6" => [machine.name], "bar" => ["#{machine.name}", "group3"], - "bar:children" => ["group1", "group2", "group3", "group4"], - "bar:vars" => ["myvar=foo"], + "bar:children" => ["group1", "group2", "group3", "group5"], } expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| inventory_content = File.read(generated_inventory_file) - # Group variables are intentionally not supported in generated inventory - expect(inventory_content).not_to match(/^\[.*:vars\]$/) - - # Accept String instead of Array for group that contains a single item - expect(inventory_content).to include("[group1]\n#{machine.name}\n") - expect(inventory_content).to include("[group1:children]\nbar\n") + # Accept String instead of Array for group member list + expect(inventory_content).to include("[group1]\nmachine1\n\n") + expect(inventory_content).to include("[group1:children]\nbar\ngroup3\n\n") # Skip "lost" machines expect(inventory_content).to include("[group2]\n\n") # Skip "unknown" machines - expect(inventory_content).to include("[group3]\n#{machine.name}\n") + expect(inventory_content).to include("[group3]\n#{machine.name}\n\n") + + # Accept Symbol datatype for group names + expect(inventory_content).to include("[group6]\n#{machine.name}\n\n") + + # Accept host range patterns + expect(inventory_content).to include("[group4]\nmachine[1:2]\nmachine[a:f]\n") # Don't mix group names and host names - expect(inventory_content).to include("[bar]\n#{machine.name}\n") + expect(inventory_content).to include("[bar]\n#{machine.name}\n\n") # A group of groups only includes declared groups - expect(inventory_content).not_to match(/^group4$/) - expect(inventory_content).to include("[bar:children]\ngroup1\ngroup2\ngroup3\n") + expect(inventory_content).not_to include("group5") + expect(inventory_content).to match(Regexp.quote("[bar:children]\ngroup1\ngroup2\ngroup3\n") + "$") + } + end + + it "adds group vars to the generated inventory" do + config.groups = { + "group1" => [machine.name], + "group2" => [machine.name], + "group3" => [machine.name], + "group1:vars" => {"hashvar1" => "hashvalue1", "hashvar2" => "hashvalue2"}, + "group2:vars" => ["arrayvar1=arrayvalue1", "arrayvar2=arrayvalue2"], + "group3:vars" => "stringvar1=stringvalue1 stringvar2=stringvalue2", + } + + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + inventory_content = File.read(generated_inventory_file) + + # Hash syntax + expect(inventory_content).to include("[group1:vars]\nhashvar1=hashvalue1\nhashvar2=hashvalue2\n") + + # Array syntax + expect(inventory_content).to include("[group2:vars]\narrayvar1=arrayvalue1\narrayvar2=arrayvalue2\n") + + # Single string syntax + expect(inventory_content).to include("[group3:vars]\nstringvar1=stringvalue1\nstringvar2=stringvalue2\n") } end end @@ -276,7 +351,7 @@ VF config.host_key_checking = true end - it_should_set_arguments_and_environment_variables 6, 4, true + it_should_set_arguments_and_environment_variables 5, 4, true end describe "with boolean (flag) options disabled" do @@ -288,7 +363,7 @@ VF config.sudo_user = 'root' end - it_should_set_arguments_and_environment_variables 7 + it_should_set_arguments_and_environment_variables 6 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 @@ -303,6 +378,7 @@ VF describe "with raw_arguments option" do before do config.sudo = false + config.force_remote_user = false config.skip_tags = %w(foo bar) config.limit = "all" config.raw_arguments = ["--connection=paramiko", @@ -352,12 +428,79 @@ VF it_should_set_arguments_and_environment_variables end + context "with force_remote_user option disabled" do + before do + config.force_remote_user = false + end + + it_should_create_and_use_generated_inventory false # i.e. without setting ansible_ssh_user in inventory + + it_should_set_arguments_and_environment_variables 6 + + it "uses a --user argument to set a default remote user" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + expect(args).not_to include("--extra-vars=ansible_ssh_user='#{machine.ssh_info[:username]}'") + expect(args).to include("--user=#{machine.ssh_info[:username]}") + } + end + end + + context "with winrm communicator" do + + let(:iso_winrm_env) do + env = isolated_environment + env.vagrantfile <<-VF +Vagrant.configure("2") do |config| + config.winrm.username = 'winner' + config.winrm.password = 'winword' + config.winrm.transport = :ssl + + config.vm.define :machine1 do |machine| + machine.vm.box = "winbox" + machine.vm.communicator = :winrm + end +end +VF + env.create_vagrant_env + end + + let(:machine) { iso_winrm_env.machine(iso_winrm_env.machine_names[0], :dummy) } + + it_should_set_arguments_and_environment_variables + + it "generates an inventory with winrm connection settings" do + + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + 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("machine1 ansible_connection=winrm ansible_ssh_host=127.0.0.1 ansible_ssh_port=55986 ansible_ssh_user='winner' ansible_ssh_pass='winword'\n") + } + end + + describe "with force_remote_user option disabled" do + before do + config.force_remote_user = false + end + + it "doesn't set the ansible remote user in inventory and use '--user' argument with the vagrant ssh username" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + inventory_content = File.read(generated_inventory_file) + + expect(inventory_content).to include("machine1 ansible_connection=winrm ansible_ssh_host=127.0.0.1 ansible_ssh_port=55986 ansible_ssh_pass='winword'\n") + expect(args).to include("--user=testuser") + } + end + end + end + describe "with inventory_path option" do before do config.inventory_path = existing_file end - it_should_set_arguments_and_environment_variables + it_should_set_arguments_and_environment_variables 6 it "does not generate the inventory and uses given inventory path instead" do expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| @@ -366,6 +509,26 @@ VF expect(File.exists?(generated_inventory_file)).to be_false } end + + it "uses an --extra-vars argument to force ansible_ssh_user parameter" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + expect(args).not_to include("--user=#{machine.ssh_info[:username]}") + expect(args).to include("--extra-vars=ansible_ssh_user='#{machine.ssh_info[:username]}'") + } + end + + describe "with force_remote_user option disabled" do + before do + config.force_remote_user = false + end + + it "uses a --user argument to set a default remote user" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + expect(args).not_to include("--extra-vars=ansible_ssh_user='#{machine.ssh_info[:username]}'") + expect(args).to include("--user=#{machine.ssh_info[:username]}") + } + end + end end describe "with ask_vault_pass option" do @@ -373,7 +536,7 @@ VF config.ask_vault_pass = true end - it_should_set_arguments_and_environment_variables 7 + it_should_set_arguments_and_environment_variables 6 it "should ask the vault password" do expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| @@ -387,7 +550,7 @@ VF config.vault_password_file = existing_file end - it_should_set_arguments_and_environment_variables 7 + it_should_set_arguments_and_environment_variables 6 it "uses the given vault password file" do expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| @@ -401,7 +564,7 @@ VF config.raw_ssh_args = ['-o ControlMaster=no', '-o ForwardAgent=no'] end - it_should_set_arguments_and_environment_variables 6, 4 + it_should_set_arguments_and_environment_variables it_should_explicitly_enable_ansible_ssh_control_persist_defaults it "passes custom SSH options via ANSIBLE_SSH_ARGS with the highest priority" do @@ -435,14 +598,14 @@ VF ssh_info[:private_key_path] = ['/path/to/my/key', '/an/other/identity', '/yet/an/other/key'] end - it_should_set_arguments_and_environment_variables 6, 4 + it_should_set_arguments_and_environment_variables 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| cmd_opts = args.last - expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentityFile=/an/other/identity") - expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentityFile=/yet/an/other/key") + expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-i '/an/other/identity'") + expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-i '/yet/an/other/key'") } end end @@ -452,7 +615,7 @@ VF ssh_info[:forward_agent] = true end - it_should_set_arguments_and_environment_variables 6, 4 + it_should_set_arguments_and_environment_variables it_should_explicitly_enable_ansible_ssh_control_persist_defaults it "enables SSH-Forwarding via ANSIBLE_SSH_ARGS" do @@ -463,19 +626,69 @@ VF end end - describe "with verbose option" do - before do - config.verbose = 'v' + context "with verbose option defined" do + %w(vv vvvv).each do |verbose_option| + + describe "with a value of '#{verbose_option}'" do + before do + config.verbose = verbose_option + end + + it_should_set_arguments_and_environment_variables 6 + it_should_set_optional_arguments({ "verbose" => "-#{verbose_option}" }) + + it "shows the ansible-playbook command and set verbosity to '-#{verbose_option}' level" do + expect(machine.env.ui).to receive(:detail).with { |full_command| + expect(full_command).to eq("PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=false ANSIBLE_SSH_ARGS='-o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --limit='machine1' --inventory-file=#{generated_inventory_dir} -#{verbose_option} playbook.yml") + } + end + end + + describe "with a value of '-#{verbose_option}'" do + before do + config.verbose = "-#{verbose_option}" + end + + it_should_set_arguments_and_environment_variables 6 + it_should_set_optional_arguments({ "verbose" => "-#{verbose_option}" }) + + it "shows the ansible-playbook command and set verbosity to '-#{verbose_option}' level" do + expect(machine.env.ui).to receive(:detail).with { |full_command| + expect(full_command).to eq("PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=false ANSIBLE_SSH_ARGS='-o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --limit='machine1' --inventory-file=#{generated_inventory_dir} -#{verbose_option} playbook.yml") + } + end + end end - it_should_set_arguments_and_environment_variables 7 - it_should_set_optional_arguments({ "verbose" => "-v" }) + describe "with an invalid string" do + before do + config.verbose = "wrong" + end - it "shows the ansible-playbook command" do - expect(machine.env.ui).to receive(:detail).with { |full_command| - 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") - } + it_should_set_arguments_and_environment_variables 6 + it_should_set_optional_arguments({ "verbose" => "-v" }) + + it "shows the ansible-playbook command and set verbosity to '-v' level" do + expect(machine.env.ui).to receive(:detail).with { |full_command| + expect(full_command).to eq("PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=false ANSIBLE_SSH_ARGS='-o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --limit='machine1' --inventory-file=#{generated_inventory_dir} -v playbook.yml") + } + end end + + describe "with an empty string" do + before do + config.verbose = "" + end + + it_should_set_arguments_and_environment_variables + + it "doesn't show the ansible-playbook command" do + expect(machine.env.ui).not_to receive(:detail).with { |full_command| + expect(full_command).to include("ansible-playbook") + } + end + end + end describe "without colorized output" do @@ -492,9 +705,35 @@ VF end end - # Note: - # The Vagrant Ansible provisioner does not validate the coherency of argument combinations, - # and let ansible-playbook complain. + describe "with galaxy support" do + + before do + config.galaxy_role_file = existing_file + end + + it "raises an error when ansible-galaxy command fails", skip_before: true, skip_after: true do + config.finalize! + Vagrant::Util::Subprocess.stub(execute: Vagrant::Util::Subprocess::Result.new(1, "", "")) + + expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleCommandFailed) + end + + it "execute ansible-galaxy and ansible-playbook" do + # TODO: to be improved, but I'm currenty facing some issues, maybe only present in RSpec 2.14... + expect(Vagrant::Util::Subprocess).to receive(:execute).twice + end + + describe "with verbose option enabled" do + before do + config.verbose = true + end + + xit "shows the ansible-galaxy command in use" + end + end + + # The Vagrant Ansible provisioner does not validate the coherency of + # argument combinations, and let ansible-playbook complain. describe "with a maximum of options" do before do # vagrant general options @@ -520,7 +759,7 @@ VF config.raw_ssh_args = ['-o ControlMaster=no'] end - it_should_set_arguments_and_environment_variables 21, 4, true + it_should_set_arguments_and_environment_variables 20, 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", @@ -547,7 +786,7 @@ VF 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("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") + expect(full_command).to eq("PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=true ANSIBLE_SSH_ARGS='-o IdentitiesOnly=yes -i '/my/key1' -i '/my/key2' -o ForwardAgent=yes -o ControlMaster=no -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --ask-sudo-pass --ask-vault-pass --limit='machine*:&vagrant:!that_one' --inventory-file=#{generated_inventory_dir} --extra-vars=@#{File.expand_path(__FILE__)} --sudo --sudo-user=deployer -vvv --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 @@ -588,7 +827,20 @@ VF # Special cases related to the Vagrant Host operating system in use # - context "with a Solaris-like host" do + context "on a Windows host" do + before do + Vagrant::Util::Platform.stub(windows?: true) + machine.ui.stub(:warn) + end + + it "warns that Windows is not officially supported for the Ansible control machine" do + expect(machine.env.ui).to receive(:warn).with { |warning| + expect(warning).to eq(I18n.t("vagrant.provisioners.ansible.windows_not_supported_for_control_machine")) + } + end + end + + context "on a Solaris-like host" do before do Vagrant::Util::Platform.stub(solaris?: true) end @@ -598,12 +850,9 @@ VF 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 + # Ending this block with a negative expectation (to_not / not_to) + # would lead to a failure of the above expectation. + true } end @@ -615,12 +864,9 @@ VF 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 + # Ending this block with a negative expectation (to_not / not_to) + # would lead to a failure of the above expectation. + true } 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 655c9041f..e2359d917 100644 --- a/test/unit/plugins/provisioners/chef/command_builder_test.rb +++ b/test/unit/plugins/provisioners/chef/command_builder_test.rb @@ -8,6 +8,8 @@ describe VagrantPlugins::Chef::CommandBuilder do let(:chef_config) { double("chef_config") } before(:each) do + allow(chef_config).to receive(:install).and_return(true) + allow(chef_config).to receive(:version).and_return("12.0.0") 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) @@ -71,6 +73,23 @@ describe VagrantPlugins::Chef::CommandBuilder do expect(subject.command).to include( " --no-color") end + + it "includes --force-formatter if Chef > 10" do + expect(subject.command).to include( + " --force-formatter") + end + + it "does not include --force-formatter if Chef < 11" do + allow(chef_config).to receive(:version).and_return("10.0") + expect(subject.command).to_not include( + " --force-formatter") + end + + it "does not include --force-formatter if we did not install Chef" do + allow(chef_config).to receive(:install).and_return(false) + expect(subject.command).to_not include( + " --force-formatter") + end end describe "linux" do @@ -113,6 +132,18 @@ describe VagrantPlugins::Chef::CommandBuilder do allow(chef_config).to receive(:binary_env).and_return("ENVVAR=VAL") expect(subject.command).to match(/^ENVVAR=VAL /) end + + it "does not include --force-formatter if Chef < 11" do + allow(chef_config).to receive(:version).and_return("10.0") + expect(subject.command).to_not include( + " --force-formatter") + end + + it "does not include --force-formatter if we did not install Chef" do + allow(chef_config).to receive(:install).and_return(false) + expect(subject.command).to_not include( + " --force-formatter") + end 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 index 8bc6c6402..65d1e74c7 100644 --- a/test/unit/plugins/provisioners/chef/config/base_runner_test.rb +++ b/test/unit/plugins/provisioners/chef/config/base_runner_test.rb @@ -205,7 +205,10 @@ describe VagrantPlugins::Chef::Config::BaseRunner do describe "#validate_base" do context "when #custom_config_path does not exist" do - let(:path) { "/path/to/file" } + let(:path) do + next "/path/to/file" if !Vagrant::Util::Platform.windows? + "C:/path/to/file" + end before do allow(File).to receive(:file?) diff --git a/test/unit/plugins/provisioners/chef/config/base_test.rb b/test/unit/plugins/provisioners/chef/config/base_test.rb index 4b018f11b..4c1a31321 100644 --- a/test/unit/plugins/provisioners/chef/config/base_test.rb +++ b/test/unit/plugins/provisioners/chef/config/base_test.rb @@ -23,6 +23,13 @@ describe VagrantPlugins::Chef::Config::Base do end end + describe "#product" do + it "defaults to \"chef\"" do + subject.finalize! + expect(subject.product).to eq("chef") + end + end + describe "#install" do it "defaults to true" do subject.finalize! @@ -49,10 +56,18 @@ describe VagrantPlugins::Chef::Config::Base do end end - describe "#prerelease" do - it "defaults to true" do + describe "#channel" do + it "defaults to \"current\"" do subject.finalize! - expect(subject.prerelease).to be(false) + expect(subject.channel).to eq("current") + end + end + + describe "#prerelease" do + it "should not exist in Vagrant 1.9" do + if Vagrant::VERSION >= "1.9" + raise "This option should be removed!" + 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 index 25034d019..402663bf3 100644 --- a/test/unit/plugins/provisioners/chef/config/chef_client_test.rb +++ b/test/unit/plugins/provisioners/chef/config/chef_client_test.rb @@ -96,41 +96,5 @@ describe VagrantPlugins::Chef::Config::ChefClient do 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 index d6a2a03af..2c699cbd2 100644 --- a/test/unit/plugins/provisioners/chef/config/chef_solo_test.rb +++ b/test/unit/plugins/provisioners/chef/config/chef_solo_test.rb @@ -57,6 +57,14 @@ describe VagrantPlugins::Chef::Config::ChefSolo do end end + describe "#nodes_path" do + it "defaults to an empty array" do + subject.finalize! + expect(subject.nodes_path).to be_a(Array) + expect(subject.nodes_path).to be_empty + end + end + describe "#synced_folder_type" do it "defaults to nil" do subject.finalize! diff --git a/test/unit/plugins/provisioners/chef/config/chef_zero_test.rb b/test/unit/plugins/provisioners/chef/config/chef_zero_test.rb index 2f9cd82aa..1767da562 100644 --- a/test/unit/plugins/provisioners/chef/config/chef_zero_test.rb +++ b/test/unit/plugins/provisioners/chef/config/chef_zero_test.rb @@ -50,6 +50,14 @@ describe VagrantPlugins::Chef::Config::ChefZero do end end + describe "#nodes_path" do + it "defaults to an empty array" do + subject.finalize! + expect(subject.nodes_path).to be_a(Array) + expect(subject.nodes_path).to be_empty + end + end + describe "#synced_folder_type" do it "defaults to nil" do subject.finalize! @@ -74,7 +82,7 @@ describe VagrantPlugins::Chef::Config::ChefZero do it "returns an error" do subject.cookbooks_path = nil subject.finalize! - expect(errors).to eq [I18n.t("vagrant.config.chef.cookbooks_path_empty")] + expect(errors).to include(I18n.t("vagrant.config.chef.cookbooks_path_empty")) end end @@ -82,7 +90,7 @@ describe VagrantPlugins::Chef::Config::ChefZero do it "returns an error" do subject.cookbooks_path = [] subject.finalize! - expect(errors).to eq [I18n.t("vagrant.config.chef.cookbooks_path_empty")] + expect(errors).to include(I18n.t("vagrant.config.chef.cookbooks_path_empty")) end end @@ -90,7 +98,31 @@ describe VagrantPlugins::Chef::Config::ChefZero 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")] + expect(errors).to include(I18n.t("vagrant.config.chef.cookbooks_path_empty")) + end + end + + context "when the nodes_path is nil" do + it "returns an error" do + subject.nodes_path = nil + subject.finalize! + expect(errors).to include(I18n.t("vagrant.config.chef.nodes_path_empty")) + end + end + + context "when the nodes_path is an empty array" do + it "returns an error" do + subject.nodes_path = [] + subject.finalize! + expect(errors).to include(I18n.t("vagrant.config.chef.nodes_path_empty")) + end + end + + context "when the nodes_path is an array with nil" do + it "returns an error" do + subject.nodes_path = [nil, nil] + subject.finalize! + expect(errors).to include(I18n.t("vagrant.config.chef.nodes_path_empty")) end end @@ -103,7 +135,7 @@ describe VagrantPlugins::Chef::Config::ChefZero do it "returns an error" do subject.environments_path = nil subject.finalize! - expect(errors).to eq [I18n.t("vagrant.config.chef.environment_path_required")] + expect(errors).to include(I18n.t("vagrant.config.chef.environment_path_required")) end end @@ -111,7 +143,7 @@ describe VagrantPlugins::Chef::Config::ChefZero do it "returns an error" do subject.environments_path = [] subject.finalize! - expect(errors).to eq [I18n.t("vagrant.config.chef.environment_path_required")] + expect(errors).to include(I18n.t("vagrant.config.chef.environment_path_required")) end end @@ -119,7 +151,7 @@ describe VagrantPlugins::Chef::Config::ChefZero 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")] + expect(errors).to include(I18n.t("vagrant.config.chef.environment_path_required")) end end @@ -128,11 +160,9 @@ describe VagrantPlugins::Chef::Config::ChefZero 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 - ) - ] + expect(errors).to include(I18n.t("vagrant.config.chef.environment_path_missing", + path: env_path, + )) end end end diff --git a/test/unit/plugins/provisioners/chef/omnibus_test.rb b/test/unit/plugins/provisioners/chef/omnibus_test.rb index b053f94bc..5e8c4aaea 100644 --- a/test/unit/plugins/provisioners/chef/omnibus_test.rb +++ b/test/unit/plugins/provisioners/chef/omnibus_test.rb @@ -3,53 +3,44 @@ 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)}" } + describe "#sh_command" do + it "includes the project name" do + command = described_class.sh_command("chef", nil, "stable") + expect(command).to include %|-P "chef"| + end - let(:version) { :latest } - let(:prerelease) { false } - let(:download_path) { nil } + it "includes the channel" do + command = described_class.sh_command("chef", nil, "stable") + expect(command).to include %|-c "stable"| + end - let(:build_command) { described_class.build_command(version, prerelease, download_path) } + it "includes the version" do + command = described_class.sh_command("chef", "1.2.3", "stable") + expect(command).to include %|-v "1.2.3"| + end - 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") + it "includes the download path" do + command = described_class.sh_command("chef", "1.2.3", "stable", + download_path: "/some/path", + ) + expect(command).to include %|-d "/some/path"| 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\"") + describe "#ps_command" do + it "includes the project name" do + command = described_class.ps_command("chef", nil, "stable") + expect(command).to include %|-project 'chef'| 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") + it "includes the channel" do + command = described_class.ps_command("chef", nil, "stable") + expect(command).to include %|-channel 'stable'| 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\"") + it "includes the version" do + command = described_class.ps_command("chef", "1.2.3", "stable") + expect(command).to include %|-version '1.2.3'| end end end diff --git a/test/unit/plugins/provisioners/chef/provisioner/base_test.rb b/test/unit/plugins/provisioners/chef/provisioner/base_test.rb index 6274ed317..033051b1b 100644 --- a/test/unit/plugins/provisioners/chef/provisioner/base_test.rb +++ b/test/unit/plugins/provisioners/chef/provisioner/base_test.rb @@ -5,11 +5,23 @@ require Vagrant.source_root.join("plugins/provisioners/chef/provisioner/base") describe VagrantPlugins::Chef::Provisioner::Base do include_context "unit" - let(:machine) { double("machine") } + 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) { iso_env.machine(iso_env.machine_names[0], :dummy) } let(:config) { double("config") } subject { described_class.new(machine, config) } + before do + allow(config).to receive(:node_name) + allow(config).to receive(:node_name=) + end + describe "#encrypted_data_bag_secret_key_path" do let(:env) { double("env") } let(:root_path) { "/my/root" } diff --git a/test/unit/plugins/provisioners/docker/config_test.rb b/test/unit/plugins/provisioners/docker/config_test.rb index e12f883c4..ba2b6836d 100644 --- a/test/unit/plugins/provisioners/docker/config_test.rb +++ b/test/unit/plugins/provisioners/docker/config_test.rb @@ -139,15 +139,10 @@ describe VagrantPlugins::DockerProvisioner::Config do end describe "#version" do - it "defaults to latest" do - subject.finalize! - expect(subject.version).to eql(:latest) - end - - it "converts to a symbol" do - subject.version = "v27" - subject.finalize! - expect(subject.version).to eql(:v27) + it "is removed in Vagrant 1.9" do + if Vagrant::VERSION >= "1.9" + raise "Remove deprecated option" + end end end end diff --git a/test/unit/plugins/provisioners/salt/config_test.rb b/test/unit/plugins/provisioners/salt/config_test.rb index 24bf24796..92e935db0 100644 --- a/test/unit/plugins/provisioners/salt/config_test.rb +++ b/test/unit/plugins/provisioners/salt/config_test.rb @@ -16,6 +16,12 @@ describe VagrantPlugins::Salt::Config do let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) } + describe "#config_dir" do + it "is deprecated until Vagrant 1.9" do + raise "Remove the deprecation" if Vagrant::VERSION >= "1.9.0" + end + end + describe "validate" do let(:error_key) { "salt provisioner" } diff --git a/test/unit/plugins/provisioners/shell/config_test.rb b/test/unit/plugins/provisioners/shell/config_test.rb index 946b4c2a8..c4d260b90 100644 --- a/test/unit/plugins/provisioners/shell/config_test.rb +++ b/test/unit/plugins/provisioners/shell/config_test.rb @@ -85,6 +85,30 @@ describe "VagrantPlugins::Shell::Config" do I18n.t("vagrant.provisioners.shell.args_bad_type") ]) end + + it "returns an error if elevated_interactive is true but privileged is false" do + subject.path = file_that_exists + subject.powershell_elevated_interactive = true + subject.privileged = false + subject.finalize! + + result = subject.validate(machine) + + expect(result["shell provisioner"]).to eq([ + I18n.t("vagrant.provisioners.shell.interactive_not_elevated") + ]) + end + + it "returns an error if the environment is not a hash" do + subject.env = "foo" + subject.finalize! + + result = subject.validate(machine) + + expect(result["shell provisioner"]).to include( + I18n.t("vagrant.provisioners.shell.env_must_be_a_hash") + ) + end end describe 'finalize!' do diff --git a/test/unit/plugins/provisioners/shell/provisioner_test.rb b/test/unit/plugins/provisioners/shell/provisioner_test.rb index 8c9201717..8f83f2554 100644 --- a/test/unit/plugins/provisioners/shell/provisioner_test.rb +++ b/test/unit/plugins/provisioners/shell/provisioner_test.rb @@ -16,6 +16,7 @@ describe "Vagrant::Shell::Provisioner" do double( :config, :args => "doesn't matter", + :env => {}, :upload_path => "arbitrary", :remote? => false, :path => nil, diff --git a/test/unit/plugins/provisioners/support/shared/config.rb b/test/unit/plugins/provisioners/support/shared/config.rb index 052a10b4a..db7aaaeaa 100644 --- a/test/unit/plugins/provisioners/support/shared/config.rb +++ b/test/unit/plugins/provisioners/support/shared/config.rb @@ -1,3 +1,9 @@ +def get_provisioner_option_names(provisioner_class) + config_options = provisioner_class.instance_methods(true).find_all { |i| i.to_s.end_with?('=') } + config_options.map! { |i| i.to_s.sub('=', '') } + (config_options - ["!", "=", "=="]).sort +end + shared_examples_for 'any VagrantConfigProvisioner strict boolean attribute' do |attr_name, attr_default_value| [true, false].each do |bool| diff --git a/test/unit/plugins/pushes/ftp/adapter_test.rb b/test/unit/plugins/pushes/ftp/adapter_test.rb index e929078cd..6ef07196a 100644 --- a/test/unit/plugins/pushes/ftp/adapter_test.rb +++ b/test/unit/plugins/pushes/ftp/adapter_test.rb @@ -39,7 +39,10 @@ describe VagrantPlugins::FTPPush::FTPAdapter do include_context "unit" before(:all) do - @server = FakeFtp::Server.new(21212, 21213) + @server = nil + with_random_port do |port1, port2| + @server = FakeFtp::Server.new(port1, port2) + end @server.start end diff --git a/test/unit/plugins/pushes/ftp/push_test.rb b/test/unit/plugins/pushes/ftp/push_test.rb index 1f6773e24..00ed3ae11 100644 --- a/test/unit/plugins/pushes/ftp/push_test.rb +++ b/test/unit/plugins/pushes/ftp/push_test.rb @@ -9,7 +9,7 @@ describe VagrantPlugins::FTPPush::Push do let(:env) { isolated_environment } let(:config) do double("config", - host: "127.0.0.1:51234", + host: "127.0.0.1:#{@port}", username: "sethvargo", password: "bacon", passive: false, @@ -34,11 +34,14 @@ describe VagrantPlugins::FTPPush::Push do describe "#push" do before(:all) do - @server = FakeFtp::Server.new(51234, 21213) + @server = nil + with_random_port do |port1, port2| + @port = port1 + @server = FakeFtp::Server.new(port1, port2) + end @server.start - @dir = Dir.mktmpdir - + @dir = temporary_dir FileUtils.touch("#{@dir}/.hidden.rb") FileUtils.touch("#{@dir}/application.rb") FileUtils.touch("#{@dir}/config.rb") @@ -48,7 +51,6 @@ describe VagrantPlugins::FTPPush::Push do end after(:all) do - FileUtils.rm_rf(@dir) @server.stop end diff --git a/test/unit/plugins/pushes/heroku/push_test.rb b/test/unit/plugins/pushes/heroku/push_test.rb index c0337e41f..a7fe11e05 100644 --- a/test/unit/plugins/pushes/heroku/push_test.rb +++ b/test/unit/plugins/pushes/heroku/push_test.rb @@ -1,5 +1,7 @@ require_relative "../../../base" +require "vagrant/util/platform" + require Vagrant.source_root.join("plugins/pushes/heroku/push") describe VagrantPlugins::HerokuPush::Push do @@ -24,10 +26,13 @@ describe VagrantPlugins::HerokuPush::Push do describe "#push" do let(:branch) { "master" } - - let(:root_path) { "/handy/dandy" } let(:dir) { "#{root_path}/#{config.dir}" } + let(:root_path) do + next "/handy/dandy" if !Vagrant::Util::Platform.windows? + "C:/handy/dandy" + end + before do allow(subject).to receive(:git_branch) .and_return(branch) @@ -252,22 +257,6 @@ describe VagrantPlugins::HerokuPush::Push do 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" } diff --git a/test/unit/plugins/pushes/local-exec/config_test.rb b/test/unit/plugins/pushes/local-exec/config_test.rb index 045872d2f..1bde10d98 100644 --- a/test/unit/plugins/pushes/local-exec/config_test.rb +++ b/test/unit/plugins/pushes/local-exec/config_test.rb @@ -26,6 +26,13 @@ describe VagrantPlugins::LocalExecPush::Config do end end + describe "#args" do + it "defaults to nil" do + subject.finalize! + expect(subject.args).to be(nil) + end + end + describe "#validate" do before do allow(machine).to receive(:env) @@ -57,6 +64,42 @@ describe VagrantPlugins::LocalExecPush::Config do it "does not return an error" do expect(errors).to be_empty end + + it "passes with string args" do + subject.args = "a string" + expect(errors).to be_empty + end + + it "passes with fixnum args" do + subject.args = 1 + expect(errors).to be_empty + end + + it "passes with array args" do + subject.args = ["an", "array"] + expect(errors).to be_empty + end + + it "returns an error if args is neither a string nor an array" do + neither_array_nor_string = Object.new + + subject.args = neither_array_nor_string + expect(errors).to include( + I18n.t("local_exec_push.errors.args_bad_type") + ) + end + + it "handles scalar array args" do + subject.args = ["string", 1, 2] + expect(errors).to be_empty + end + + it "returns an error if args is an array with non-scalar types" do + subject.args = [[1]] + expect(errors).to include( + I18n.t("local_exec_push.errors.args_bad_type") + ) + end end end @@ -69,6 +112,42 @@ describe VagrantPlugins::LocalExecPush::Config do it "does not return an error" do expect(errors).to be_empty end + + it "passes with string args" do + subject.args = "a string" + expect(errors).to be_empty + end + + it "passes with fixnum args" do + subject.args = 1 + expect(errors).to be_empty + end + + it "passes with array args" do + subject.args = ["an", "array"] + expect(errors).to be_empty + end + + it "returns an error if args is neither a string nor an array" do + neither_array_nor_string = Object.new + + subject.args = neither_array_nor_string + expect(errors).to include( + I18n.t("local_exec_push.errors.args_bad_type") + ) + end + + it "handles scalar array args" do + subject.args = ["string", 1, 2] + expect(errors).to be_empty + end + + it "returns an error if args is an array with non-scalar types" do + subject.args = [[1]] + expect(errors).to include( + I18n.t("local_exec_push.errors.args_bad_type") + ) + end end context "when inline is not present" do diff --git a/test/unit/plugins/pushes/local-exec/push_test.rb b/test/unit/plugins/pushes/local-exec/push_test.rb index 1efdfa3b1..00db78777 100644 --- a/test/unit/plugins/pushes/local-exec/push_test.rb +++ b/test/unit/plugins/pushes/local-exec/push_test.rb @@ -15,6 +15,7 @@ describe VagrantPlugins::LocalExecPush::Push do double("config", script: nil, inline: nil, + args: "some args", ) end @@ -37,7 +38,7 @@ describe VagrantPlugins::LocalExecPush::Push do it "executes the inline script" do expect(subject).to receive(:execute_inline!) - .with(config.inline) + .with(config.inline, config.args) subject.push end end @@ -47,7 +48,7 @@ describe VagrantPlugins::LocalExecPush::Push do it "executes the script" do expect(subject).to receive(:execute_script!) - .with(config.script) + .with(config.script, config.args) subject.push end end @@ -58,12 +59,12 @@ describe VagrantPlugins::LocalExecPush::Push do it "writes the script to a tempfile" do expect(Tempfile).to receive(:new).and_call_original - subject.execute_inline!("echo") + subject.execute_inline!("echo", config.args) end it "executes the script" do expect(subject).to receive(:execute_script!) - subject.execute_inline!("echo") + subject.execute_inline!("echo", config.args) end end @@ -76,26 +77,50 @@ describe VagrantPlugins::LocalExecPush::Push do 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") + subject.execute_script!("./foo.sh", nil) 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") + subject.execute_script!("./foo.sh", config.args) end it "calls execute!" do expect(subject).to receive(:execute!) .with(File.expand_path("foo.sh", env.root_path)) - subject.execute_script!("./foo.sh") + subject.execute_script!("./foo.sh", nil) + end + + context "when args is given" do + it "passes string args to execute!" do + expect(subject).to receive(:execute!) + .with(File.expand_path("foo.sh", env.root_path) + " " + config.args) + subject.execute_script!("./foo.sh", config.args) + end + + it "passes array args as string to execute!" do + expect(subject).to receive(:execute!) + .with(File.expand_path("foo.sh", env.root_path) + " \"one\" \"two\" \"three\"") + subject.execute_script!("./foo.sh", ["one", "two", "three"]) + end end end describe "#execute!" do - it "safe execs" do + it "uses exec on unix" do + allow(Vagrant::Util::Platform).to receive(:windows?).and_return(false) expect(Vagrant::Util::SafeExec).to receive(:exec) expect { subject.execute! }.to_not raise_error end + + it "uses subprocess on windows" do + allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true) + result = double("result", exit_code: 0) + expect(Vagrant::Util::Subprocess).to receive(:execute).and_return(result) + expect { subject.execute! }.to raise_error { |e| + expect(e).to be_a(SystemExit) + } + end end end diff --git a/test/unit/support/shared/base_context.rb b/test/unit/support/shared/base_context.rb index bd7f3f517..c84b6ae1d 100644 --- a/test/unit/support/shared/base_context.rb +++ b/test/unit/support/shared/base_context.rb @@ -1,6 +1,8 @@ require "tempfile" require "tmpdir" +require "vagrant/util/platform" + require "unit/support/isolated_environment" shared_context "unit" do @@ -58,18 +60,16 @@ shared_context "unit" do # # @return [Pathname] def temporary_file(contents=nil) - f = Tempfile.new("vagrant-unit") + dir = temporary_dir + f = dir.join("tempfile") - if contents + contents ||= "" + f.open("w") do |f| f.write(contents) f.flush end - # Store the tempfile in an instance variable so that it is not - # garbage collected, so that the tempfile is not unlinked. - @_temp_files << f - - return Pathname.new(f.path) + return Pathname.new(Vagrant::Util::Platform.fs_real_path(f.to_s)) end # This creates a temporary directory and returns a {Pathname} @@ -80,10 +80,13 @@ shared_context "unit" do # Create a temporary directory and append it to the instance # variabe so that it isn't garbage collected and deleted d = Dir.mktmpdir("vagrant") + @_temp_files ||= [] @_temp_files << d # Return the pathname - return Pathname.new(d) + result = Pathname.new(Vagrant::Util::Platform.fs_real_path(d)) + yield result if block_given? + return result end # Stub the given environment in ENV, without actually touching ENV. Keys and @@ -104,6 +107,7 @@ shared_context "unit" do # can replace them back in later. old_env = {} environment.each do |key, value| + key = key.to_s old_env[key] = ENV[key] ENV[key] = value end @@ -116,4 +120,18 @@ shared_context "unit" do ENV[key] = value end end + + # This helper provides a randomly available port(s) for each argument to the + # block. + def with_random_port(&block) + ports = [] + + block.arity.times do + server = TCPServer.new('127.0.0.1', 0) + ports << server.addr[1] + server.close + end + + block.call(*ports) + end end diff --git a/test/unit/templates/guests/suse/network_dhcp_test.rb b/test/unit/templates/guests/suse/network_dhcp_test.rb index 82595db47..1ecb4dc1f 100644 --- a/test/unit/templates/guests/suse/network_dhcp_test.rb +++ b/test/unit/templates/guests/suse/network_dhcp_test.rb @@ -12,9 +12,9 @@ describe "templates/guests/suse/network_dhcp" do 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 + BOOTPROTO='dhcp' + STARTMODE='auto' + DEVICE='ethen0' #VAGRANT-END EOH end diff --git a/test/unit/vagrant/action/builtin/box_add_test.rb b/test/unit/vagrant/action/builtin/box_add_test.rb index ad3a56dfc..db3c7044f 100644 --- a/test/unit/vagrant/action/builtin/box_add_test.rb +++ b/test/unit/vagrant/action/builtin/box_add_test.rb @@ -4,11 +4,13 @@ require "tempfile" require "tmpdir" require "webrick" +require "fake_ftp" + require File.expand_path("../../../../base", __FILE__) require "vagrant/util/file_checksum" -describe Vagrant::Action::Builtin::BoxAdd do +describe Vagrant::Action::Builtin::BoxAdd, :skip_windows do include_context "unit" let(:app) { lambda { |env| } } @@ -34,6 +36,25 @@ describe Vagrant::Action::Builtin::BoxAdd do FileChecksum.new(path, Digest::SHA1).checksum end + def with_ftp_server(path, **opts) + path = Pathname.new(path) + + tf = Tempfile.new("vagrant") + tf.close + + port = nil + server = nil + with_random_port do |port1, port2| + port = port1 + server = FakeFtp::Server.new(port1, port2) + end + server.add_file(path.basename, path.read) + server.start + yield port + ensure + server.stop rescue nil + end + def with_web_server(path, **opts) tf = Tempfile.new("vagrant") tf.close @@ -123,6 +144,26 @@ describe Vagrant::Action::Builtin::BoxAdd do end end + it "adds from FTP URL" do + box_path = iso_env.box2_file(:virtualbox) + with_ftp_server(box_path) do |port| + env[:box_name] = "foo" + env[:box_url] = "ftp://127.0.0.1:#{port}/#{box_path.basename}" + + expect(box_collection).to receive(:add).with { |path, name, version, **opts| + expect(checksum(path)).to eq(checksum(box_path)) + expect(name).to eq("foo") + expect(version).to eq("0") + expect(opts[:metadata_url]).to be_nil + true + }.and_return(box) + + expect(app).to receive(:call).with(env) + + subject.call(env) + end + end + it "raises an error if no name is given" do box_path = iso_env.box2_file(:virtualbox) @@ -166,6 +207,28 @@ describe Vagrant::Action::Builtin::BoxAdd do to raise_error(Vagrant::Errors::BoxChecksumMismatch) end + it "does not raise an error if the checksum has different case" do + box_path = iso_env.box2_file(:virtualbox) + + box = double( + name: "foo", + version: "1.2.3", + provider: "virtualbox", + ) + + env[:box_name] = box.name + env[:box_url] = box_path.to_s + env[:box_checksum] = checksum(box_path) + env[:box_checksum_type] = "sha1" + + # Convert to a different case + env[:box_checksum].upcase! + + expect(box_collection).to receive(:add).and_return(box) + + expect { subject.call(env) }.to_not raise_error + end + it "raises an error if the box path doesn't exist" do box_path = iso_env.box2_file(:virtualbox) diff --git a/test/unit/vagrant/action/builtin/box_check_outdated_test.rb b/test/unit/vagrant/action/builtin/box_check_outdated_test.rb index bcb313d1c..0bb175c59 100644 --- a/test/unit/vagrant/action/builtin/box_check_outdated_test.rb +++ b/test/unit/vagrant/action/builtin/box_check_outdated_test.rb @@ -117,7 +117,9 @@ describe Vagrant::Action::Builtin::BoxCheckOutdated do } RAW - expect(box).to receive(:has_update?).with(machine.config.vm.box_version). + expect(box).to receive(:has_update?).with(machine.config.vm.box_version, + {download_options: + {ca_cert: nil, ca_path: nil, client_cert: nil, insecure: false}}). and_return([md, md.version("1.1"), md.version("1.1").provider("virtualbox")]) expect(app).to receive(:call).with(env).once @@ -180,5 +182,38 @@ describe Vagrant::Action::Builtin::BoxCheckOutdated do expect { subject.call(env) }.to_not raise_error end + + context "when machine download options are specified" do + before do + machine.config.vm.box_download_ca_cert = "foo" + machine.config.vm.box_download_ca_path = "bar" + machine.config.vm.box_download_client_cert = "baz" + machine.config.vm.box_download_insecure = true + end + + it "uses download options from machine" do + expect(box).to receive(:has_update?).with(machine.config.vm.box_version, + {download_options: + {ca_cert: "foo", ca_path: "bar", client_cert: "baz", insecure: true}}) + + expect(app).to receive(:call).with(env).once + + subject.call(env) + end + + it "overrides download options from machine with options from env" do + expect(box).to receive(:has_update?).with(machine.config.vm.box_version, + {download_options: + {ca_cert: "oof", ca_path: "rab", client_cert: "zab", insecure: false}}) + + env[:ca_cert] = "oof" + env[:ca_path] = "rab" + env[:client_cert] = "zab" + env[:insecure] = false + expect(app).to receive(:call).with(env).once + + subject.call(env) + end + end end end diff --git a/test/unit/vagrant/action/builtin/box_remove_test.rb b/test/unit/vagrant/action/builtin/box_remove_test.rb index 9de3e467b..f1a740218 100644 --- a/test/unit/vagrant/action/builtin/box_remove_test.rb +++ b/test/unit/vagrant/action/builtin/box_remove_test.rb @@ -30,6 +30,8 @@ describe Vagrant::Action::Builtin::BoxRemove do expect(box_collection).to receive(:find).with( "foo", :virtualbox, "1.0").and_return(box) + expect(box_collection).to receive(:clean).with(box.name) + .and_return(true) expect(box).to receive(:destroy!).once expect(app).to receive(:call).with(env).once @@ -50,6 +52,8 @@ describe Vagrant::Action::Builtin::BoxRemove do expect(box_collection).to receive(:find).with( "foo", :virtualbox, "1.0").and_return(box) + expect(box_collection).to receive(:clean).with(box.name) + .and_return(false) expect(box).to receive(:destroy!).once expect(app).to receive(:call).with(env).once @@ -70,6 +74,8 @@ describe Vagrant::Action::Builtin::BoxRemove do expect(box_collection).to receive(:find).with( "foo", :virtualbox, "1.0").and_return(box) + expect(box_collection).to receive(:clean).with(box.name) + .and_return(false) expect(box).to receive(:destroy!).once expect(app).to receive(:call).with(env).once @@ -110,6 +116,8 @@ describe Vagrant::Action::Builtin::BoxRemove do expect(box_collection).to receive(:find).with( "foo", :virtualbox, "1.0").and_return(box) expect(box).to receive(:destroy!).once + expect(box_collection).to receive(:clean).with(box.name) + .and_return(true) subject.call(env) end @@ -123,6 +131,8 @@ describe Vagrant::Action::Builtin::BoxRemove do expect(box_collection).to receive(:find).with( "foo", :virtualbox, "1.0").and_return(box) + expect(box_collection).to receive(:clean).with(box.name) + .and_return(true) expect(box).to receive(:destroy!).once subject.call(env) 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 264e158d2..67a5eca27 100644 --- a/test/unit/vagrant/action/builtin/mixin_synced_folders_test.rb +++ b/test/unit/vagrant/action/builtin/mixin_synced_folders_test.rb @@ -114,11 +114,13 @@ describe Vagrant::Action::Builtin::MixinSyncedFolders do result = subject.synced_folders(machine) expect(result.length).to eq(2) expect(result[:default]).to eq({ - "another" => folders["another"], - "foo" => folders["foo"], - "root" => folders["root"], + "another" => folders["another"].merge(__vagrantfile: true), + "foo" => folders["foo"].merge(__vagrantfile: true), + "root" => folders["root"].merge(__vagrantfile: true), + }) + expect(result[:nfs]).to eq({ + "nfs" => folders["nfs"].merge(__vagrantfile: true), }) - expect(result[:nfs]).to eq({ "nfs" => folders["nfs"] }) end it "should return the proper set of folders of a custom config" do @@ -185,16 +187,20 @@ describe Vagrant::Action::Builtin::MixinSyncedFolders do result = subject.synced_folders(machine, cached: true) expect(result.length).to eq(2) expect(result[:default]).to eq({ - "another" => old_folders["another"], - "foo" => old_folders["foo"], - "root" => old_folders["root"], + "another" => old_folders["another"].merge(__vagrantfile: true), + "foo" => old_folders["foo"].merge(__vagrantfile: true), + "root" => old_folders["root"].merge(__vagrantfile: true), }) - expect(result[:nfs]).to eq({ "nfs" => old_folders["nfs"] }) + expect(result[:nfs]).to eq({ "nfs" => old_folders["nfs"].merge(__vagrantfile: true) }) end it "should be able to save and retrieve cached versions" do - folders["foo"] = { type: "default" } - result = subject.synced_folders(machine) + other_folders = {} + other = double("config") + other.stub(synced_folders: other_folders) + + other_folders["foo"] = { type: "default" } + result = subject.synced_folders(machine, config: other) subject.save_synced_folders(machine, result) # Clear the folders and set some more @@ -212,10 +218,36 @@ describe Vagrant::Action::Builtin::MixinSyncedFolders do expect(result.length).to eq(2) expect(result[:default]).to eq({ "foo" => { type: "default" }, - "bar" => { type: "default" }, + "bar" => { type: "default", __vagrantfile: true}, }) expect(result[:nfs]).to eq({ - "baz" => { type: "nfs" } + "baz" => { type: "nfs", __vagrantfile: true } + }) + end + + it "should remove items from the vagrantfile that were removed" do + folders["foo"] = { type: "default" } + result = subject.synced_folders(machine) + subject.save_synced_folders(machine, result) + + # Clear the folders and set some more + folders.clear + folders["bar"] = { type: "default" } + folders["baz"] = { type: "nfs" } + result = subject.synced_folders(machine) + subject.save_synced_folders(machine, result, merge: true, vagrantfile: true) + + # Clear one last time + folders.clear + + # Read them all back + result = subject.synced_folders(machine, cached: true) + expect(result.length).to eq(2) + expect(result[:default]).to eq({ + "bar" => { type: "default", __vagrantfile: true}, + }) + expect(result[:nfs]).to eq({ + "baz" => { type: "nfs", __vagrantfile: true } }) end end diff --git a/test/unit/vagrant/action/builtin/provisioner_cleanup_test.rb b/test/unit/vagrant/action/builtin/provisioner_cleanup_test.rb new file mode 100644 index 000000000..2d3b907c7 --- /dev/null +++ b/test/unit/vagrant/action/builtin/provisioner_cleanup_test.rb @@ -0,0 +1,84 @@ +require "pathname" +require "tmpdir" + +require File.expand_path("../../../../base", __FILE__) + +describe Vagrant::Action::Builtin::ProvisionerCleanup do + let(:app) { lambda { |env| } } + let(:env) { { machine: machine, ui: ui } } + + let(:machine) do + double("machine").tap do |machine| + allow(machine).to receive(:config).and_return(machine_config) + end + end + + let(:machine_config) do + double("machine_config").tap do |config| + config.stub(vm: vm_config) + end + end + + let(:vm_config) { double("machine_vm_config") } + + let(:ui) do + double("ui").tap do |result| + allow(result).to receive(:info) + end + end + + let(:provisioner) do + Class.new(Vagrant.plugin("2", :provisioner)) + end + + before do + allow_any_instance_of(described_class).to receive(:provisioner_type_map) + .and_return(provisioner => :test_provisioner) + allow_any_instance_of(described_class).to receive(:provisioner_instances) + .and_return([provisioner]) + end + + describe "initialize with :before" do + it "runs cleanup before" do + instance = described_class.new(app, env, :before) + expect(provisioner).to receive(:cleanup).ordered + expect(app).to receive(:call).ordered + instance.call(env) + end + end + + describe "initialize with :after" do + it "runs cleanup after" do + instance = described_class.new(app, env, :after) + expect(app).to receive(:call).ordered + expect(provisioner).to receive(:cleanup).ordered + instance.call(env) + end + end + + it "only runs cleanup tasks if the subclass defines it" do + parent = Class.new do + class_variable_set(:@@cleanup, false) + + def self.called? + class_variable_get(:@@cleanup) + end + + def cleanup + self.class.class_variable_set(:@@cleanup) + end + end + + child = Class.new(parent) + + allow_any_instance_of(described_class).to receive(:provisioner_type_map) + .and_return(child => :test_provisioner) + allow_any_instance_of(described_class).to receive(:provisioner_instances) + .and_return([child]) + + expect(parent.called?).to be(false) + instance = described_class.new(app, env) + instance.call(env) + expect(parent.called?).to be(false) + end +end diff --git a/test/unit/vagrant/action/builtin/synced_folders_test.rb b/test/unit/vagrant/action/builtin/synced_folders_test.rb index 0eb0719cb..5e606b005 100644 --- a/test/unit/vagrant/action/builtin/synced_folders_test.rb +++ b/test/unit/vagrant/action/builtin/synced_folders_test.rb @@ -197,5 +197,32 @@ describe Vagrant::Action::Builtin::SyncedFolders do expect(ids.length).to eq(2) expect(ids[0]).to eq(ids[1]) end + + context "with folders from the machine" do + it "removes outdated folders not present in config" do + expect(subject).to receive(:save_synced_folders).with( + machine, anything, merge: true, vagrantfile: true) + + subject.call(env) + end + end + + context "with custom folders" do + before do + new_config = double("config") + env[:synced_folders_config] = new_config + + allow(subject).to receive(:synced_folders). + with(machine, config: new_config, cached: false). + and_return({}) + end + + it "doesn't remove outdated folders not present in config" do + expect(subject).to receive(:save_synced_folders).with( + machine, anything, merge: true) + + subject.call(env) + end + end end end diff --git a/test/unit/vagrant/box_collection_test.rb b/test/unit/vagrant/box_collection_test.rb index a09d23559..f0e5e0edc 100644 --- a/test/unit/vagrant/box_collection_test.rb +++ b/test/unit/vagrant/box_collection_test.rb @@ -3,7 +3,7 @@ require File.expand_path("../../base", __FILE__) require "pathname" require 'tempfile' -describe Vagrant::BoxCollection do +describe Vagrant::BoxCollection, :skip_windows do include_context "unit" let(:box_class) { Vagrant::Box } @@ -44,6 +44,65 @@ describe Vagrant::BoxCollection do end end + describe "#clean" do + it "removes the directory if no other versions of the box exists" do + # Create a few boxes, immediately destroy them + environment.box3("foo", "1.0", :virtualbox) + environment.box3("foo", "1.0", :vmware) + + # Delete them all + subject.all.each do |parts| + subject.find(parts[0], parts[2], ">= 0").destroy! + end + + # Cleanup + subject.clean("foo") + + # Make sure the whole directory is empty + expect(environment.boxes_dir.children).to be_empty + end + + it "doesn't remove the directory if a provider exists" do + # Create a few boxes, immediately destroy them + environment.box3("foo", "1.0", :virtualbox) + environment.box3("foo", "1.0", :vmware) + + # Delete them all + subject.find("foo", :virtualbox, ">= 0").destroy! + + # Cleanup + subject.clean("foo") + + # Make sure the whole directory is not empty + expect(environment.boxes_dir.children).to_not be_empty + + # Make sure the results still exist + results = subject.all + expect(results.length).to eq(1) + expect(results.include?(["foo", "1.0", :vmware])).to be + end + + it "doesn't remove the directory if a version exists" do + # Create a few boxes, immediately destroy them + environment.box3("foo", "1.0", :virtualbox) + environment.box3("foo", "1.2", :virtualbox) + + # Delete them all + subject.find("foo", :virtualbox, ">= 1.1").destroy! + + # Cleanup + subject.clean("foo") + + # Make sure the whole directory is not empty + expect(environment.boxes_dir.children).to_not be_empty + + # Make sure the results still exist + results = subject.all + expect(results.length).to eq(1) + expect(results.include?(["foo", "1.0", :virtualbox])).to be + end + end + describe "#find" do it "returns nil if the box does not exist" do expect(subject.find("foo", :i_dont_exist, ">= 0")).to be_nil diff --git a/test/unit/vagrant/box_test.rb b/test/unit/vagrant/box_test.rb index 07af56acb..f5fa7dac3 100644 --- a/test/unit/vagrant/box_test.rb +++ b/test/unit/vagrant/box_test.rb @@ -6,7 +6,7 @@ require "tempfile" require "vagrant/box_metadata" -describe Vagrant::Box do +describe Vagrant::Box, :skip_windows do include_context "unit" let(:environment) { isolated_environment } diff --git a/test/unit/vagrant/environment_test.rb b/test/unit/vagrant/environment_test.rb index 6a595fb57..37a2d0caf 100644 --- a/test/unit/vagrant/environment_test.rb +++ b/test/unit/vagrant/environment_test.rb @@ -69,15 +69,15 @@ describe Vagrant::Environment do describe "#home_path" do it "is set to the home path given" do - Dir.mktmpdir do |dir| + temporary_dir do |dir| instance = described_class.new(home_path: dir) expect(instance.home_path).to eq(Pathname.new(dir)) end end it "is set to the environmental variable VAGRANT_HOME" do - Dir.mktmpdir do |dir| - instance = with_temp_env("VAGRANT_HOME" => dir) do + temporary_dir do |dir| + instance = with_temp_env("VAGRANT_HOME" => dir.to_s) do described_class.new end @@ -905,8 +905,13 @@ VF end it "is expanded relative to the cwd" do - instance = described_class.new(local_data_path: "foo") - expect(instance.local_data_path).to eq(instance.cwd.join("foo")) + Dir.mktmpdir do |temp_dir| + Dir.chdir(temp_dir) do + instance = described_class.new(local_data_path: "foo") + expect(instance.local_data_path).to eq(instance.cwd.join("foo")) + expect(File.exist?(instance.local_data_path)).to be_false + end + end end it "is set to the given value" do @@ -933,7 +938,7 @@ VF end expect { instance }.to_not raise_error - expect(Pathname.new(local_data_path)).to be_directory + expect(Pathname.new(local_data_path)).to_not be_exist end it "should upgrade all active VMs" do diff --git a/test/unit/vagrant/machine_test.rb b/test/unit/vagrant/machine_test.rb index 4064fa1d5..f3ec97d4a 100644 --- a/test/unit/vagrant/machine_test.rb +++ b/test/unit/vagrant/machine_test.rb @@ -3,6 +3,8 @@ require "tmpdir" require File.expand_path("../../base", __FILE__) +require "vagrant/util/platform" + describe Vagrant::Machine do include_context "unit" @@ -70,6 +72,15 @@ describe Vagrant::Machine do expect(subject.id).to be_nil end + describe "as a base" do + let(:base) { true} + + it "should not insert key" do + subject = new_instance + expect(subject.config.ssh.insert_key).to be_false + end + end + describe "communicator loading" do it "doesn't eager load SSH" do config.vm.communicator = :ssh @@ -683,14 +694,17 @@ describe Vagrant::Machine do end it "should return the private key in the Vagrantfile if the data dir exists" do + path = "/foo" + path = "C:/foo" if Vagrant::Util::Platform.windows? + provider_ssh_info[:private_key_path] = nil - instance.config.ssh.private_key_path = "/foo" + instance.config.ssh.private_key_path = path instance.data_dir.join("private_key").open("w+") do |f| f.write("hey") end - expect(instance.ssh_info[:private_key_path]).to eql(["/foo"]) + expect(instance.ssh_info[:private_key_path]).to eql([path]) end context "with no data dir" do diff --git a/test/unit/vagrant/plugin/v2/command_test.rb b/test/unit/vagrant/plugin/v2/command_test.rb index 7b42f2345..fc6cc5271 100644 --- a/test/unit/vagrant/plugin/v2/command_test.rb +++ b/test/unit/vagrant/plugin/v2/command_test.rb @@ -263,6 +263,35 @@ describe Vagrant::Plugin::V2::Command do expect(results.length).to eq(1) expect(results[0].id).to eq(other_machine.id) end + + it "should yield machines from another environment" do + iso_env = isolated_environment + iso_env.vagrantfile("") + other_env = iso_env.create_vagrant_env( + home_path: environment.home_path) + other_machine = other_env.machine( + other_env.machine_names[0], other_env.default_provider) + + # Set an ID on it so that it is "created" in the index + other_machine.id = "foo" + + # Grab the uuid so we know what it is + index_uuid = other_machine.index_uuid + + # Remove the working directory + FileUtils.rm_rf(iso_env.workdir) + + # Make sure we don't have a root path, to test + environment.stub(root_path: nil) + + # Run the command + expect { + subject.with_target_vms(index_uuid) { |*args| } + }.to raise_error(Vagrant::Errors::EnvironmentNonExistentCWD) + + # Verify that it no longer exists in the index + expect(other_env.machine_index.get(index_uuid)).to be_nil + end end describe "splitting the main and subcommand args" do diff --git a/test/unit/vagrant/ui_test.rb b/test/unit/vagrant/ui_test.rb index 61bfe7270..8ac03437e 100644 --- a/test/unit/vagrant/ui_test.rb +++ b/test/unit/vagrant/ui_test.rb @@ -313,7 +313,7 @@ describe Vagrant::UI::Prefixed do describe "#ask" do it "does not request bolding" do - expect(ui).to receive(:ask).with(" #{prefix}: foo", bold: false) + expect(ui).to receive(:ask).with(" #{prefix}: foo", bold: false, target: prefix) subject.ask("foo") end end @@ -325,12 +325,13 @@ describe Vagrant::UI::Prefixed do end it "prefixes every line" do - expect(ui).to receive(:detail).with(" #{prefix}: foo\n #{prefix}: bar", bold: false) + expect(ui).to receive(:detail).with( + " #{prefix}: foo\n #{prefix}: bar", bold: false, target: prefix) subject.detail("foo\nbar") end it "doesn't prefix if requested" do - expect(ui).to receive(:detail).with("foo", prefix: false, bold: false) + expect(ui).to receive(:detail).with("foo", prefix: false, bold: false, target: prefix) subject.detail("foo", prefix: false) end end @@ -371,18 +372,18 @@ describe Vagrant::UI::Prefixed do end it "doesn't prefix if requestsed" do - expect(ui).to receive(:output).with("foo", prefix: false, bold: true) + expect(ui).to receive(:output).with("foo", prefix: false, bold: true, target: prefix) subject.output("foo", prefix: false) end it "requests bolding" do - expect(ui).to receive(:output).with("==> #{prefix}: foo", bold: true) + expect(ui).to receive(:output).with("==> #{prefix}: foo", bold: true, target: prefix) subject.output("foo") end it "does not request bolding if class-level disabled" do ui.opts[:bold] = false - expect(ui).to receive(:output).with("==> #{prefix}: foo", {}) + expect(ui).to receive(:output).with("==> #{prefix}: foo", target: prefix) subject.output("foo") end diff --git a/test/unit/vagrant/util/platform_test.rb b/test/unit/vagrant/util/platform_test.rb index ae51076ec..267886712 100644 --- a/test/unit/vagrant/util/platform_test.rb +++ b/test/unit/vagrant/util/platform_test.rb @@ -3,8 +3,43 @@ require File.expand_path("../../../base", __FILE__) require "vagrant/util/platform" describe Vagrant::Util::Platform do + include_context "unit" + subject { described_class } + describe "#cygwin?" do + before do + allow(subject).to receive(:platform).and_return("test") + end + + around do |example| + with_temp_env(VAGRANT_DETECTED_OS: "nope", PATH: "") do + example.run + end + end + + it "returns true if VAGRANT_DETECTED_OS includes cygwin" do + with_temp_env(VAGRANT_DETECTED_OS: "cygwin") do + expect(subject).to be_cygwin + end + end + + it "returns true if platform has cygwin" do + allow(subject).to receive(:platform).and_return("cygwin") + expect(subject).to be_cygwin + end + + it "returns true if the PATH contains cygwin" do + with_temp_env(PATH: "C:/cygwin") do + expect(subject).to be_cygwin + end + end + + it "returns false if nothing is available" do + expect(subject).to_not be_cygwin + end + end + describe "#fs_real_path" do it "fixes drive letters on Windows", :windows do expect(described_class.fs_real_path("c:/foo").to_s).to eql("C:/foo") diff --git a/test/unit/vagrant/util/presence_test.rb b/test/unit/vagrant/util/presence_test.rb new file mode 100644 index 000000000..8be6e87c1 --- /dev/null +++ b/test/unit/vagrant/util/presence_test.rb @@ -0,0 +1,50 @@ +require File.expand_path("../../../base", __FILE__) + +require "vagrant/util/presence" + +describe Vagrant::Util::Presence do + subject { described_class } + + describe "#presence" do + it "returns false for nil" do + expect(subject.presence(nil)).to be(false) + end + + it "returns false for false" do + expect(subject.presence(false)).to be(false) + end + + it "returns false for an empty string" do + expect(subject.presence("")).to be(false) + end + + it "returns false for a string with null bytes" do + expect(subject.presence("\u0000")).to be(false) + end + + it "returns false for an empty array" do + expect(subject.presence([])).to be(false) + end + + it "returns false for an array with nil values" do + expect(subject.presence([nil, nil])).to be(false) + end + + it "returns false for an empty hash" do + expect(subject.presence({})).to be(false) + end + + it "returns true for true" do + expect(subject.presence(true)).to be(true) + end + + it "returns the object for an object" do + obj = Object.new + expect(subject.presence(obj)).to be(obj) + end + + it "returns the class for a class" do + expect(subject.presence(String)).to be(String) + end + end +end diff --git a/vagrant.gemspec b/vagrant.gemspec index b0f0638e3..078add344 100644 --- a/vagrant.gemspec +++ b/vagrant.gemspec @@ -22,7 +22,7 @@ Gem::Specification.new do |s| 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-ssh", "~> 3.0.1" s.add_dependency "net-sftp", "~> 2.1" s.add_dependency "net-scp", "~> 1.1.0" s.add_dependency "rb-kqueue", "~> 0.2.0" diff --git a/website/docs/Makefile b/website/docs/Makefile new file mode 100644 index 000000000..63bb4cab1 --- /dev/null +++ b/website/docs/Makefile @@ -0,0 +1,10 @@ +all: build + +init: + bundle + +dev: init + bundle exec middleman server + +build: init + bundle exec middleman build \ No newline at end of file diff --git a/website/docs/README.md b/website/docs/README.md index ca4c038fe..e9ac25640 100644 --- a/website/docs/README.md +++ b/website/docs/README.md @@ -14,13 +14,7 @@ requests like any normal GitHub project, and we'll merge it in. ## Running the Site Locally -Running the site locally is simple. Clone this repo and run the following -commands: +Running the site locally is simple. Clone this repo and run `make dev`. -``` -$ bundle -$ bundle exec middleman server -``` - -Then open up `localhost:4567`. Note that some URLs you may need to append +Then open up `localhost:4567/v2`. Note that some URLs you may need to append ".html" to make them work (in the navigation and such). diff --git a/website/docs/source/layouts/layout.erb b/website/docs/source/layouts/layout.erb index 259577d79..db40d692b 100644 --- a/website/docs/source/layouts/layout.erb +++ b/website/docs/source/layouts/layout.erb @@ -47,6 +47,7 @@
+
@@ -103,6 +104,8 @@ >login >package >plugin + >port + >powershell >provision >rdp >reload @@ -160,6 +163,7 @@ >File >Shell >Ansible + >Ansible Local >CFEngine >Chef Solo >Chef Zero @@ -266,6 +270,7 @@ <% end %> @@ -325,6 +330,11 @@
  • Documentation
  • About
  • Support
  • + <% relative_path = current_page.path.match(/.*\//).to_s + file = current_page.source_file.split("/").last + github_link = "https://github.com/mitchellh/vagrant/blob/master/website/docs/source/#{relative_path}#{file}" + %> +
  • Edit this page
  • Download
  • diff --git a/website/docs/source/stylesheets/_mixins.less b/website/docs/source/stylesheets/_mixins.less index eb0afdeeb..45dd9e745 100644 --- a/website/docs/source/stylesheets/_mixins.less +++ b/website/docs/source/stylesheets/_mixins.less @@ -63,6 +63,15 @@ padding: @baseline 0; } +.fixed-bg { + position: fixed; + top: 0; + width: 100%; + height: 100%; + z-index: -5; //keep it in the back + will-change: transform; +} + .inner-bg-large { background-image: #c1b4d5; /* Old browsers */ background-image: url(/images/sidebar_background_inner.png), -moz-linear-gradient(45deg, #c1b4d5 0%, #98d3f8 100%); /* FF3.6+ */ @@ -74,7 +83,6 @@ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#c1b4d5', endColorstr='#98d3f8',GradientType=1 ); /* IE6-8 fallback on horizontal gradient */ background-repeat: no-repeat; background-position: 0 0; - background-attachment:fixed; } .inner-bg-small { @@ -99,7 +107,6 @@ background-image: url(/images/sidebar_background_docs.png), linear-gradient(45deg, #362d6c 0%,#0c5593 100%); /* W3C */ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#362d6c', endColorstr='#0c5593',GradientType=1 ); /* IE6-8 fallback on horizontal gradient */ background-repeat: no-repeat; - background-attachment:fixed; } diff --git a/website/docs/source/stylesheets/_pages.less b/website/docs/source/stylesheets/_pages.less index 5daab0892..ef67c02b3 100644 --- a/website/docs/source/stylesheets/_pages.less +++ b/website/docs/source/stylesheets/_pages.less @@ -2,11 +2,7 @@ .page { //style all pages .page-background { //page background color - width: 100%; - height: 100%; - top: 0; - position: fixed; - z-index: -5; //keep it in the back + .fixed-bg; } //.background .page-contents { @@ -181,7 +177,9 @@ /* inner */ &.inner { //style inner pages - .inner-bg-large; //sidebar background + .sidebar-background { //sidebar background + .inner-bg-large; + } .page-background { //change inner background color! background-color: @gray-background; //page background @@ -205,12 +203,14 @@ /* docs */ &.docs { //style all docs - .docs-bg-large; //sidebar background - .page-background { //change the sidebar background color! background: @white; //page background } + .sidebar-background { //sidebar background + .docs-bg-large; + } + .page-contents { padding-top: 30px; background-color: @white; diff --git a/website/docs/source/stylesheets/_sidebar.less b/website/docs/source/stylesheets/_sidebar.less index 356deac79..70fc39a7a 100644 --- a/website/docs/source/stylesheets/_sidebar.less +++ b/website/docs/source/stylesheets/_sidebar.less @@ -7,6 +7,10 @@ margin-bottom: 20px; } + &-background { //background image + .fixed-bg; + } + .button { font-size: 15px; color: @dark-blue-text; diff --git a/website/docs/source/v2/cli/destroy.html.md b/website/docs/source/v2/cli/destroy.html.md index 598380cc3..33ec1665b 100644 --- a/website/docs/source/v2/cli/destroy.html.md +++ b/website/docs/source/v2/cli/destroy.html.md @@ -12,8 +12,11 @@ destroys all resources that were created during the machine creation process. After running this command, your computer should be left at a clean state, as if you never created the guest machine in the first place. -This command usually asks for confirmation before destroying. This -confirmation can be skipped by passing in the `-f` or `--force` flag. +For linux-based guests, Vagrant uses the `shutdown` command to gracefully +terminate the machine. Due to the varying nature of operating systems, the +`shutdown` command may exist at many different locations in the guest's `$PATH`. +It is the guest machine's responsibility to properly populate the `$PATH` with +directory containing the `shutdown` command. ## Options diff --git a/website/docs/source/v2/cli/halt.html.md b/website/docs/source/v2/cli/halt.html.md index 15091f8a8..0c30a0972 100644 --- a/website/docs/source/v2/cli/halt.html.md +++ b/website/docs/source/v2/cli/halt.html.md @@ -13,6 +13,12 @@ Vagrant will first attempt to gracefully shut down the machine by running the guest OS shutdown mechanism. If this fails, or if the `--force` flag is specified, Vagrant will effectively just shut off power to the machine. +For linux-based guests, Vagrant uses the `shutdown` command to gracefully +terminate the machine. Due to the varying nature of operating systems, the +`shutdown` command may exist at many different locations in the guest's `$PATH`. +It is the guest machine's responsibility to properly populate the `$PATH` with +directory containing the `shutdown` command. + ## Options * `-f` or `--force` - Don't attempt to gracefully shut down the machine. diff --git a/website/docs/source/v2/cli/machine-readable.html.md b/website/docs/source/v2/cli/machine-readable.html.md index 52642eeb1..b3257ea66 100644 --- a/website/docs/source/v2/cli/machine-readable.html.md +++ b/website/docs/source/v2/cli/machine-readable.html.md @@ -122,6 +122,15 @@ with the machine-readable output. + +ssh-config + + The OpenSSH compatible SSH config for a machine. This is usually + the result of the "ssh-config" command. + targeted + + + state diff --git a/website/docs/source/v2/cli/package.html.md b/website/docs/source/v2/cli/package.html.md index 0409cbeb1..76b6a081b 100644 --- a/website/docs/source/v2/cli/package.html.md +++ b/website/docs/source/v2/cli/package.html.md @@ -8,9 +8,10 @@ sidebar_current: "cli-package" **Command: `vagrant package`** This packages a currently running _VirtualBox_ environment into a -re-usable [box](/v2/boxes.html). This command cannot be used with -any other [provider](/v2/providers/index.html). A future version of Vagrant -will address packaging boxes for other providers. Until then, they must +re-usable [box](/v2/boxes.html). This command can only be used with +other [providers](/v2/providers/index.html) based on the provider implementation +and if the provider supports it. A future version of Vagrant will +address packaging boxes for other providers. Until then, they must be made by hand. ## Options diff --git a/website/docs/source/v2/cli/plugin.html.md b/website/docs/source/v2/cli/plugin.html.md index a92cc42cb..f38a8af9a 100644 --- a/website/docs/source/v2/cli/plugin.html.md +++ b/website/docs/source/v2/cli/plugin.html.md @@ -42,6 +42,10 @@ This command accepts optional command-line flags: Most of the time, this is correct. If the plugin you're installing has another entrypoint, this flag can be used to specify it. +* `--plugin-clean-sources` - Clears all sources that have been defined so + far. This is an advanced feature. The use case is primarily for corporate + firewalls that prevent access to RubyGems.org. + * `--plugin-source SOURCE` - Adds a source from which to fetch a plugin. Note that this doesn't only affect the single plugin being installed, by all future plugin as well. This is a limitation of the underlying plugin installer diff --git a/website/docs/source/v2/cli/port.html.md b/website/docs/source/v2/cli/port.html.md new file mode 100644 index 000000000..97f815de8 --- /dev/null +++ b/website/docs/source/v2/cli/port.html.md @@ -0,0 +1,35 @@ +--- +page_title: "vagrant port - Command-Line Interface" +sidebar_current: "cli-port" +--- + +# Port + +**Command: `vagrant port`** + +The port command displays the full list of guest ports mapped to the host +machine ports: + +``` +$ vagrant port + 22 (guest) => 2222 (host) + 80 (guest) => 8080 (host) +``` + +In a multi-machine Vagrantfile, the name of the machine must be specified: + +``` +$ vagrant port my-machine +``` + +## Options + +* `--guest PORT` - This displays just the host port that corresponds to the + given guest port. If the guest is not forwarding that port, an error is + returned. This is useful for quick scripting, for example: + + $ ssh -p $(vagrant port --guest 22) + +* `--machine-readable` - This tells Vagrant to display machine-readable output + instead of the human-friendly output. More information is available in the + [machine-readable output](/v2/cli/machine-readable.html) documentation. diff --git a/website/docs/source/v2/cli/powershell.html.md b/website/docs/source/v2/cli/powershell.html.md new file mode 100644 index 000000000..98e37acbc --- /dev/null +++ b/website/docs/source/v2/cli/powershell.html.md @@ -0,0 +1,19 @@ +--- +page_title: "vagrant powershell - Command-Line Interface" +sidebar_current: "cli-powershell" +--- + +# PowerShell + +**Command: `vagrant powershell`** + +This will open a PowerShell prompt into a running Vagrant machine. + +This command will only work if the machine supports PowerShell. Not every +environment will support PowerShell. + +## Options + +* `-c COMMAND` or `--command COMMAND` - This executes a single PowerShell command, + prints out the stdout and stderr, and exits. + diff --git a/website/docs/source/v2/docker/basics.html.md b/website/docs/source/v2/docker/basics.html.md index 9f4dd97a6..493fc5d7a 100644 --- a/website/docs/source/v2/docker/basics.html.md +++ b/website/docs/source/v2/docker/basics.html.md @@ -65,6 +65,9 @@ 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. +The Docker provider does not support specifying options for `owner` or `group` +on folders synced with a docker container. + Private and public networks are not currently supported. ## Host VM diff --git a/website/docs/source/v2/networking/forwarded_ports.html.md b/website/docs/source/v2/networking/forwarded_ports.html.md index 0e08e63dc..e3fef4ea9 100644 --- a/website/docs/source/v2/networking/forwarded_ports.html.md +++ b/website/docs/source/v2/networking/forwarded_ports.html.md @@ -28,6 +28,11 @@ end This will allow accessing port 80 on the guest via port 8080 on the host. +For most providers, forwarded ports by default bind to all interfaces. This +means that other devices on your network can access the forwarded ports. +If you want to restrict access, see the `guest_ip` and `host_ip` settings +below. + ## Options Reference This is a complete list of the options that are available for forwarded diff --git a/website/docs/source/v2/networking/private_network.html.md b/website/docs/source/v2/networking/private_network.html.md index e11c7b3c7..31502396f 100644 --- a/website/docs/source/v2/networking/private_network.html.md +++ b/website/docs/source/v2/networking/private_network.html.md @@ -118,3 +118,8 @@ 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. + +The files created by Vagrant depend on the OS. For example, for many +Linux distros, this is `/etc/network/interfaces`. In general you should +look in the normal location that network interfaces are configured for your +distro. diff --git a/website/docs/source/v2/networking/public_network.html.md b/website/docs/source/v2/networking/public_network.html.md index e52cbaf58..91b2443b7 100644 --- a/website/docs/source/v2/networking/public_network.html.md +++ b/website/docs/source/v2/networking/public_network.html.md @@ -51,6 +51,18 @@ When DHCP is used, the IP can be determined by using `vagrant ssh` to SSH into the machine and using the appropriate command line tool to find the IP, such as `ifconfig`. +### Using the DHCP Assigned Default Route + +Some cases require the DHCP assigned default route to be untouched. In these cases one +may specify the `use_dhcp_assigned_default_route` option. As an example: + +```ruby +Vagrant.configure("2") do |config| + config.vm.network "public_network", + use_dhcp_assigned_default_route: true +end +``` + ## Static IP Depending on your setup, you may wish to manually set the IP of your diff --git a/website/docs/source/v2/other/environmental-variables.html.md b/website/docs/source/v2/other/environmental-variables.html.md index 866b4369a..5ff4e670c 100644 --- a/website/docs/source/v2/other/environmental-variables.html.md +++ b/website/docs/source/v2/other/environmental-variables.html.md @@ -17,6 +17,16 @@ 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\_DEFAULT\_PROVIDER + +This configures the default provider Vagrant will use. + +This normally doesn't need to be set since Vagrant is fairly intelligent +about how to detect the default provider. By setting this, you will force +Vagrant to use this provider for any _new_ Vagrant environments. Existing +Vagrant environments will continue to use the provider they came `up` with. +Once you `vagrant destroy` existing environments, this will take effect. + ## VAGRANT\_CHECKPOINT\_DISABLE Vagrant does occasional network calls to check whether the version of Vagrant diff --git a/website/docs/source/v2/plugins/action-hooks.html.md b/website/docs/source/v2/plugins/action-hooks.html.md new file mode 100644 index 000000000..e5c775bf7 --- /dev/null +++ b/website/docs/source/v2/plugins/action-hooks.html.md @@ -0,0 +1,100 @@ +--- +page_title: "Plugin Development Basics - Action Hooks" +sidebar_current: "plugins-action-hooks" +--- + +# Action Hooks + +Action hooks provide ways to interact with Vagrant at a very low level by +injecting middleware in various phases of Vagrant's lifecycle. This is an +advanced option, even for plugin development. + +
    +

    + Warning: Advanced Topic! Developing plugins is an + advanced topic that only experienced Vagrant users who are reasonably + comfortable with Ruby should approach. +

    +
    + + +## Public Action Hooks + +The following action hooks are available in the core of Vagrant. Please note +that this list is not exhaustive and additional hooks can be added via plugins. + +- `environment_plugins_loaded` - called after the plugins have been loaded, + but before the configurations, provisioners, providers, etc. are loaded. + + +- `environment_load` - called after the environment and all configurations are + fully loaded. + + +- `environment_unload` - called after the environment is done being used. The + environment should not be used in this hook. + + +- `machine_action_boot` - called after the hypervisor has reported the machine + was booted. + + +- `machine_action_config_validate` - called after all `Vagrantfile`s have been + loaded, merged, and validated. + + +- `machine_action_destroy` - called after the hypervisor has reported the + virtual machine is down. + + +- `machine_action_halt` - called after the hypervision has moved the machine + into a halted state (usually "stopped" but not "terminated"). + + +- `machine_action_package` - called after Vagrant has successfully packaged a + new box. + + +- `machine_action_provision` - called after all provisioners have executed. + + +- `machine_action_read_state` - called after Vagrant has loaded state from + disk and the hypervisor. + + +- `machine_action_reload` - called after a virtual machine is reloaded (varies + by hypervisor). + + +- `machine_action_resume` - called after a virtual machine is moved from the + halted to up state. + + +- `machine_action_run_command` - called after a command is executed on the + machine. + + +- `machine_action_ssh` - called after an SSH connection has been established. + + +- `machine_action_ssh_run` - called after an SSH command is executed. + + +- `machine_action_start` - called after the machine has been started. + + +- `machine_action_suspend` - called after the machine has been suspended. + + +- `machine_action_sync_folders` - called after synced folders have been set up. + + +- `machine_action_up` - caled after the machine has entered the up state. + + +## Private API + +You may find additional action hooks if you browse the Vagrant source code, but +only the list of action hooks here are guaranteed to persist between Vagrant +releases. Please do not rely on the internal API as it is subject to change +without notice. diff --git a/website/docs/source/v2/provisioning/ansible.html.md b/website/docs/source/v2/provisioning/ansible.html.md index 381cf5bd1..f3ce97e1b 100644 --- a/website/docs/source/v2/provisioning/ansible.html.md +++ b/website/docs/source/v2/provisioning/ansible.html.md @@ -5,16 +5,9 @@ sidebar_current: "provisioning-ansible" # Ansible Provisioner -**Provisioner name: `"ansible"`** +**Provisioner name: `ansible`** -The ansible provisioner allows you to provision the guest using -[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 -page will not go into how to use Ansible or how to write Ansible playbooks, since Ansible -is a complete deployment and configuration management system that is beyond the scope of -a single page of documentation. +The Ansible provisioner allows you to provision the guest using [Ansible](http://ansible.com) playbooks by executing **`ansible-playbook` from the Vagrant host**.

    @@ -27,199 +20,62 @@ 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) + - **[Install Ansible](http://docs.ansible.com/intro_installation.html#installing-the-control-machine) on your Vagrant host**. -## Inventory File + - 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). -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. +If installing Ansible directly on the Vagrant host is not an option in your development environment, you might be looking for the Ansible Local provisioner alternative. -### Auto-Generated Inventory +## Usage -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 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`. +This page only documents the specific parts of the `ansible` (remote) provisioner. General Ansible concepts like Playbook or Inventory are shortly explained in the [introduction to Ansible and Vagrant](/v2/provisioning/ansible_intro.html). -**Groups of Hosts** +### Simplest Configuration -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"], - "all_groups:children" => ["group1", "group2"] -} -``` - -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 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 - -[group2] -machine2 - -[all_groups:children] -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. - -A very simple inventory file for use with Vagrant might look like: - -``` -default ansible_ssh_host=192.168.111.222 -``` - -Where the above IP address is one set in your Vagrantfile: - -``` -config.vm.network :private_network, ip: "192.168.111.222" -``` - -**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 - -The second component of a successful Ansible provisioner setup is the Ansible playbook -which contains the steps that should be run on the guest. Ansible's -[playbook documentation](http://docs.ansible.com/playbooks.html) goes into great -detail on how to author playbooks, and there are a number of -[best practices](http://docs.ansible.com/playbooks_best_practices.html) that can be applied to use -Ansible's powerful features effectively. A playbook that installs and starts (or restarts -if it was updated) the NTP daemon via YUM looks like: - -``` ---- -- hosts: all - tasks: - - name: ensure ntpd is at the latest version - yum: pkg=ntp state=latest - notify: - - restart ntpd - handlers: - - name: restart ntpd - service: name=ntpd state=restarted -``` - -You can of course target other operating systems that don't have YUM by changing the -playbook tasks. Ansible ships with a number of [modules](http://docs.ansible.com/modules.html) -that make running otherwise tedious tasks dead simple. - -## Running Ansible - -To run Ansible against your Vagrant guest, the basic Vagrantfile configuration looks like: +To run Ansible against your Vagrant guest, the basic `Vagrantfile` configuration looks like: ```ruby -Vagrant.configure("2") do |config| +Vagrant.configure(2) do |config| + + # + # Run Ansible from the Vagrant Host + # config.vm.provision "ansible" do |ansible| ansible.playbook = "playbook.yml" end + end ``` -Since an Ansible playbook can include many files, you may also collect the related files in -a directory structure like this: +## Options -``` -$ tree -. -|-- Vagrantfile -|-- provisioning -| |-- group_vars -| |-- all -| |-- playbook.yml -``` +This section lists the specific options for the Ansible (remote) provisioner. In addition to the options listed below, this provisioner supports the [common options for both Ansible provisioners](/v2/provisioning/ansible_common.html). -In such an arrangement, the `ansible.playbook` path should be adjusted accordingly: +- `ask_sudo_pass` (boolean) - require Ansible to [prompt for a sudo password](http://docs.ansible.com/intro_getting_started.html#remote-connection-information). -```ruby -Vagrant.configure("2") do |config| - config.vm.provision "ansible" do |ansible| - ansible.playbook = "provisioning/playbook.yml" - end -end -``` + The default value is `false`. -Vagrant will try to run the `playbook.yml` playbook against all machines defined in your Vagrantfile. +- `ask_vault_pass` (boolean) - require Ansible to [prompt for a vault password](http://docs.ansible.com/playbooks_vault.html#vault). -**Backward Compatibility Note**: + The default value is `false`. -Up to Vagrant 1.4, the Ansible provisioner could potentially connect (multiple times) to all hosts from the inventory file. -This behaviour is still possible by setting `ansible.limit = 'all'` (see more details below). +- `force_remote_user` (boolean) - require Vagrant to set the `ansible_ssh_user` setting in the generated inventory, or as an extra variable when a static inventory is used. All the Ansible `remote_user` parameters will then be overridden by the value of `config.ssh.username` of the [Vagrant SSH Settings](/v2/vagrantfile/ssh_settings.html). -## Additional Options + If this option is set to `false` Vagrant will set the Vagrant SSH username as a default Ansible remote user, but `remote_user` parameters of your Ansible plays or tasks will still be taken into account and thus override the Vagrant configuration. -The Ansible provisioner also includes a number of additional options that can be set, -all of which get passed to the `ansible-playbook` command that ships with Ansible. + The default value is `true`. -* `ansible.extra_vars` can be used to pass additional variables (with highest priority) to the playbook. This parameter can be a path to a JSON or YAML file, or a hash. For example: + **Note:** This option was introduced in Vagrant 1.8.0. Previous Vagrant versions behave like if this option was set to `false`. - ``` - ansible.extra_vars = { - ntp_server: "pool.ntp.org", - nginx: { - port: 8008, - workers: 4 - } - } - ``` - These variables take the highest precedence over any other variables. -* `ansible.sudo` can be set to `true` to cause Ansible to perform commands using sudo. -* `ansible.sudo_user` can be set to a string containing a username on the guest who should be used -by the sudo command. -* `ansible.ask_sudo_pass` can be set to `true` to require Ansible to prompt for a sudo password. -* `ansible.ask_vault_pass` can be set to `true` to require Ansible to prompt for a vault password. -* `ansible.vault_password_file` can be set to a string containing the path of a file containing the password used by Ansible Vault. -* `ansible.limit` can be set to a string or an array of machines or groups from the inventory file to further control which hosts are affected. Note that: - * As of Vagrant 1.5, the machine name (taken from Vagrantfile) is set as **default limit** to ensure that `vagrant provision` steps only affect the expected machine. Setting `ansible.limit` will override this default. - * Setting `ansible.limit = 'all'` can be used to make Ansible connect to all machines from the inventory file. -* `ansible.verbose` can be set to increase Ansible's verbosity to obtain detailed logging: - * `'v'`, verbose mode - * `'vv'` - * `'vvv'`, more - * `'vvvv'`, connection debugging -* `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. 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 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`). +- `host_key_checking` (boolean) - require Ansible to [enable SSH host key checking](http://docs.ansible.com/intro_getting_started.html#host-key-checking). + + The default value is `false`. + +- `raw_ssh_args` (array of strings) - require Ansible to apply a list of OpenSSH client options. + + Example: `['-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, overriding any other SSH arguments (e.g. defined in an [`ansible.cfg` configuration file](http://docs.ansible.com/intro_configuration.html#ssh-args)). ## Tips and Tricks @@ -265,46 +121,12 @@ end 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. - -As `ansible-playbook` command looks for local `ansible.cfg` configuration file in its *current directory* (but not in the directory that contains the main playbook), you have to store this file adjacent to your Vagrantfile. - -Note that it is also possible to reference an Ansible configuration file via `ANSIBLE_CONFIG` environment variable, if you want to be flexible about the location of this file. - -### Why does the Ansible provisioner connect as the wrong user? - -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 -* `remote_user` task attribute - -Be aware that copying snippets from the Ansible documentation might lead to this problem, as `root` is used as the remote user in many [examples](http://docs.ansible.com/playbooks_intro.html#hosts-and-users). - -Example of an SSH error (with `vvv` log level), where an undefined remote user `xyz` has replaced `vagrant`: - -``` -TASK: [my_role | do something] ***************** -<127.0.0.1> ESTABLISH CONNECTION FOR USER: xyz -<127.0.0.1> EXEC ['ssh', '-tt', '-vvv', '-o', 'ControlMaster=auto',... -fatal: [ansible-devbox] => SSH encountered an unknown error. We recommend you re-run the command using -vvvv, which will enable SSH debugging output to help diagnose the issue. -``` - -In a situation like the above, to override the `remote_user` specified in a play you can use the following line in your Vagrantfile `vm.provision` block: - -``` -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: ``` diff --git a/website/docs/source/v2/provisioning/ansible_common.html.md b/website/docs/source/v2/provisioning/ansible_common.html.md new file mode 100644 index 000000000..44b787e7e --- /dev/null +++ b/website/docs/source/v2/provisioning/ansible_common.html.md @@ -0,0 +1,134 @@ +--- +page_title: "Common Ansible Options - Provisioning" +sidebar_current: "provisioning-ansible-common" +--- + +# Shared Ansible Options + +The following options are available to both Ansible provisioners: + + - [`ansible`](/v2/provisioning/ansible.html) + - [`ansible_local`](/v2/provisioning/ansible_local.html) + +These options get passed to the `ansible-playbook` command that ships with Ansible, either via command line arguments or environment variables, depending on Ansible own capabilities. + +Some of these options are for advanced usage only and should not be used unless you understand their purpose. + +- `extra_vars` (string or hash) - Pass additional variables (with highest priority) to the playbook. + + This parameter can be a path to a JSON or YAML file, or a hash. + + Example: + + ```ruby + ansible.extra_vars = { + ntp_server: "pool.ntp.org", + nginx: { + port: 8008, + workers: 4 + } + } + ``` + These variables take the highest precedence over any other variables. + +- `host_vars` (hash) - Set of inventory host variables to be included in the [auto-generated inventory file](http://docs.ansible.com/ansible/intro_inventory.html#host-variables). + + Example: + + ```ruby + ansible.host_vars = { + "host1" => {"http_port" => 80, + "maxRequestsPerChild" => 808}, + "host2" => {"http_port" => 303, + "maxRequestsPerChild" => 909} + } + ``` + + Notes: + + - This option has no effect when the `inventory_path` option is defined. + +- `groups` (hash) - Set of inventory groups to be included in the [auto-generated inventory file](/v2/provisioning/ansible_intro.html). + + Example: + + ```ruby + ansible.groups = { + "web" => ["vm1", "vm2"], + "db" => ["vm3"] + } + ``` + Example with [group variables](http://docs.ansible.com/ansible/intro_inventory.html#group-variables): + + ```ruby + ansible.groups = { + "atlanta" => ["host1", "host2"], + "atlanta:vars" => {"ntp_server" => "ntp.atlanta.example.com", + "proxy" => "proxy.atlanta.example.com"} + } + ``` + + Notes: + + - Alphanumeric patterns are not supported (e.g. `db-[a:f]`, `vm[01:10]`). + - This option has no effect when the `inventory_path` option is defined. + +- `inventory_path` (string) - The path to an Ansible inventory resource (e.g. a [static inventory file](http://docs.ansible.com/intro_inventory.html), 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)). + + By default, this option is disabled and Vagrant generates an inventory based on the `Vagrantfile` information. + +- `galaxy_command` (template string) - The command pattern used to install Galaxy roles when `galaxy_role_file` is set. + + The following (optional) placeholders can be used in this command pattern: + - `%{role_file}` is replaced by the absolute path to the `galaxy_role_file` option + - `%{roles_path}` is + - replaced by the absolute path to the `galaxy_roles_path` option when such option is defined, or + - replaced by the absolute path to a `roles` subdirectory sitting in the `playbook` parent directory. + + By default, this option is set to + + `ansible-galaxy install --role-file=%{role_file} --roles-path=%{roles_path} --force` + +- `galaxy_role_file` (string) - The path to the Ansible Galaxy role file. + + By default, this option is set to `nil` and Galaxy support is then disabled. + + Note: if an absolute path is given, the `ansible_local` provisioner will assume that it corresponds to the exact location on the guest system. + +- `galaxy_roles_path` (string) - The path to the directory where Ansible Galaxy roles must be installed + + By default, this option is set to `nil`, which means that the Galaxy roles will be installed in a `roles` subdirectory located in the parent directory of the `playbook` file. + +- `limit` (string or array of strings) - Set of machines or groups from the inventory file to further control which hosts [are affected](http://docs.ansible.com/glossary.html#limit-groups). + + The default value is set to the machine name (taken from `Vagrantfile`) to ensure that `vagrant provision` command only affect the expected machine. + + Setting `limit = "all"` can be used to make Ansible connect to all machines from the inventory file. + +- `raw_arguments` (array of strings) - a list of additional `ansible-playbook` arguments. + + 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. + + Example: `['--check', '-M /my/modules']`). + +- `skip_tags` (string or array of strings) - Only plays, roles and tasks that [*do not match* these values will be executed](http://docs.ansible.com/playbooks_tags.html). + +- `start_at_task` (string) - The task name where the [playbook execution will start](http://docs.ansible.com/playbooks_startnstep.html#start-at-task). + +- `sudo` (boolean) - Cause Ansible to perform all the playbook tasks [using sudo](http://docs.ansible.com/glossary.html#sudo). + + The default value is `false`. + +- `sudo_user` (string) - set the default username who should be used by the sudo command. + +- `tags` (string or array of strings) - Only plays, roles and tasks [tagged with these values will be executed](http://docs.ansible.com/playbooks_tags.html) . + +- `verbose` (boolean or string) - Set Ansible's verbosity to obtain detailed logging + + Default value is `false` (minimal verbosity). + + Examples: `true` (equivalent to `v`), `-vvv` (equivalent to `vvv`), `vvvv`. + + Note that when the `verbose` option is enabled, the `ansible-playbook` command used by Vagrant will be displayed. + +- `vault_password_file` (string) - The path of a file containing the password used by [Ansible Vault](http://docs.ansible.com/playbooks_vault.html#vault). diff --git a/website/docs/source/v2/provisioning/ansible_intro.html.md b/website/docs/source/v2/provisioning/ansible_intro.html.md new file mode 100644 index 000000000..cf8d4d01e --- /dev/null +++ b/website/docs/source/v2/provisioning/ansible_intro.html.md @@ -0,0 +1,253 @@ +--- +page_title: "Ansible - Short Introduction" +sidebar_current: "provisioning-ansible-intro" +--- + +# Ansible and Vagrant + +The information below is applicable to both Ansible provisioners: + + - [`ansible`](/v2/provisioning/ansible.html), where Ansible is executed on the **Vagrant host** + + - [`ansible_local`](/v2/provisioning/ansible_local.html), where Ansible is executed on the **Vagrant guest** + +The list of common options for these two provisioners is documented in a [separate documentation page](/v2/provisioning/ansible_common.html). + +This documentation page will not go into how to use Ansible or how to write Ansible playbooks, since Ansible is a complete deployment and configuration management system that is beyond the scope of Vagrant documentation. + +To learn more about Ansible, please consult the [Ansible Documentation Site](http://docs.ansible.com/). + +## The Playbook File + +The first component of a successful Ansible provisioner setup is the Ansible playbook which contains the steps that should be run on the guest. Ansible's +[playbook documentation](http://docs.ansible.com/playbooks.html) goes into great detail on how to author playbooks, and there are a number of [best practices](http://docs.ansible.com/playbooks_best_practices.html) that can be applied to use Ansible's powerful features effectively. + +A playbook that installs and starts (or restarts) the NTP daemon via YUM looks like: + +``` +--- +- hosts: all + tasks: + - name: ensure ntpd is at the latest version + yum: pkg=ntp state=latest + notify: + - restart ntpd + handlers: + - name: restart ntpd + service: name=ntpd state=restarted +``` + +You can of course target other operating systems that don't have YUM by changing the playbook tasks. Ansible ships with a number of [modules](http://docs.ansible.com/modules.html) that make running otherwise tedious tasks dead simple. + +### Running Ansible + +The `playbook` option is strictly required by both Ansible provisioners ([`ansible`](/v2/provisioning/ansible.html) and [`ansible_local`](/v2/provisioning/ansible_local.html)), as illustrated in this basic Vagrantfile` configuration: + +```ruby +Vagrant.configure(2) do |config| + + # Use :ansible or :ansible_local to + # select the provisioner of your choice + config.vm.provision :ansible do |ansible| + ansible.playbook = "playbook.yml" + end +end +``` + +Since an Ansible playbook can include many files, you may also collect the related files in a [directory structure](http://docs.ansible.com/playbooks_best_practices.html#directory-layout) like this: + +``` +. +|-- Vagrantfile +|-- provisioning +| |-- group_vars +| |-- all +| |-- roles +| |-- bar +| |-- foo +| |-- playbook.yml +``` + +In such an arrangement, the `ansible.playbook` path should be adjusted accordingly: + +```ruby +Vagrant.configure(2) do |config| + config.vm.provision "ansible" do |ansible| + ansible.playbook = "provisioning/playbook.yml" + end +end +``` + +## The 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. + +### 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 machines it manages, and use it for provisioning machines. + +**Example with the [`ansible`](/v2/provisioning/ansible.html) provisioner:** + +``` +# Generated by Vagrant + +default ansible_ssh_host=127.0.0.1 ansible_ssh_port=2200 ansible_ssh_user='vagrant' ansible_ssh_private_key_file='/home/.../.vagrant/machines/default/virtualbox/private_key' +``` + +Note that the generated inventory file is stored as part of your local Vagrant environment in +`.vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory`. + +**Example with the [`ansible_local`](/v2/provisioning/ansible_local.html) provisioner:** + +``` +# Generated by Vagrant + +default ansible_connection=local +``` + +Note that the generated inventory file is uploaded to the guest VM in a subdirectory of [`tmp_path`](/v2/provisioning/ansible_local.html), e.g. `/tmp/vagrant-ansible/inventory/vagrant_ansible_local_inventory`. + +**Host variables:** + +As of Vagrant 1.8.0, the [`host_vars`](/v2/provisioning/ansible_common.html) option can be used to set [variables for individual hosts](http://docs.ansible.com/ansible/intro_inventory.html#host-variables) in the generated inventory file (see also the notes on group variables below). + +``` +Vagrant.configure(2) do |config| + config.vm.define "host1" + config.vm.define "host2" + config.vm.provision "ansible" do |ansible| + ansible.playbook = "playbook.yml" + ansible.host_vars = { + "host1" => {"http_port" => 80, + "maxRequestsPerChild" => 808}, + "host2" => {"http_port" => 303, + "maxRequestsPerChild" => 909} + } + end +end +``` + +Generated inventory: + +``` +# Generated by Vagrant + +host1 ansible_ssh_host=... http_port=80 maxRequestsPerChild=808 +host2 ansible_ssh_host=... http_port=303 maxRequestsPerChild=909 +``` + +**How to generate Inventory Groups:** + +The [`groups`](/v2/provisioning/ansible_common.html) option can be used to pass a hash of group names and group members to be included in the generated inventory file. + +As of Vagrant 1.8.0, it is also possible to specify [group variables](http://docs.ansible.com/ansible/intro_inventory.html#group-variables), and group members as [host ranges (with numeric or alphabetic patterns)](http://docs.ansible.com/ansible/intro_inventory.html#hosts-and-groups). + +With this configuration example: + +``` +Vagrant.configure(2) do |config| + + config.vm.box = "ubuntu/trusty64" + + config.vm.define "machine1" + config.vm.define "machine2" + + config.vm.provision "ansible" do |ansible| + ansible.playbook = "playbook.yml" + ansible.groups = { + "group1" => ["machine1"], + "group2" => ["machine2"], + "group3" => ["machine[1:2]"], + "group4" => ["other_node-[a:d]"], # silly group definition + "all_groups:children" => ["group1", "group2"], + "group1:vars" => {"variable1" => 9, + "variable2" => "example"} + } + end +end +``` + +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 ansible_ssh_user='vagrant' ansible_ssh_private_key_file='/home/.../.vagrant/machines/machine1/virtualbox/private_key' +machine2 ansible_ssh_host=127.0.0.1 ansible_ssh_port=2222 ansible_ssh_user='vagrant' ansible_ssh_private_key_file='/home/.../.vagrant/machines/machine2/virtualbox/private_key' + +[group1] +machine1 + +[group2] +machine2 + +[group3] +machine[1:2] + +[group4] +other_node-[a:d] + +[all_groups:children] +group1 +group2 + +[group1:vars] +variable1=9 +variable2=example +``` + +**Notes:** + + - 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. + - The generation of group variables blocks (e.g. `[group1:vars]`) is only possible since Vagrant 1.8.0. Note however that setting variables directly in the inventory is not the [preferred practice in Ansible](http://docs.ansible.com/intro_inventory.html#splitting-out-host-and-group-specific-data). If possible, group (or host) variables should be set in `YAML` files stored in the `group_vars/` or `host_vars/` directories in the playbook (or inventory) directory instead. + - Unmanaged machines and undefined groups are not added to the inventory, to avoid useless Ansible errors (e.g. *unreachable host* or *undefined child group*) + + For example, `machine3` and `group3` in the example below would not be added to the generated inventory file: + + ```ruby + ansible.groups = { + "group1" => ["machine1"], + "group2" => ["machine2", "machine3"], + "all_groups:children" => ["group1", "group2", "group3"] + } + ``` + - [Host range patterns (numeric and alphabetic ranges)](http://docs.ansible.com/ansible/intro_inventory.html#hosts-and-groups) will not be validated by Vagrant. As of Vagrant 1.8.0, host range patterns will be added as group members to the inventory anyway, this might lead to errors in Ansible (e.g *unreachable host*). + +### Static Inventory + +The second option is for situations where you'd like to have more control over the inventory management. + +With the `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. + +A very simple inventory file for use with Vagrant might look like: + +``` +default ansible_ssh_host=192.168.111.222 +``` + +Where the above IP address is one set in your Vagrantfile: + +``` +config.vm.network :private_network, ip: "192.168.111.222" +``` + +**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. + - Sharing hostnames across Vagrant host and guests might be a good idea (e.g. with some Ansible configuration task, or with a plugin like [`vagrant-hostmanager`](https://github.com/smdahlen/vagrant-hostmanager)). + +### The Ansible Configuration 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. + +When shipping an Ansible configuration file it is good to know that: + + - it is possible to reference an Ansible configuration file via `ANSIBLE_CONFIG` environment variable, if you want to be flexible about the location of this file. + - `ansible-playbook` **never** looks for `ansible.cfg` in the directory that contains the main playbook file. + - As of Ansible 1.5, the lookup order is the following: + + - `ANSIBLE_CONFIG` an environment variable + - `ansible.cfg` in the runtime current directory + - `.ansible.cfg` in the user home directory + - `/etc/ansible/ansible.cfg` diff --git a/website/docs/source/v2/provisioning/ansible_local.html.md b/website/docs/source/v2/provisioning/ansible_local.html.md new file mode 100644 index 000000000..1c68ddbef --- /dev/null +++ b/website/docs/source/v2/provisioning/ansible_local.html.md @@ -0,0 +1,147 @@ +--- +page_title: "Ansible Local - Provisioning" +sidebar_current: "provisioning-ansible-local" +--- + +# Ansible Local Provisioner + +**Provisioner name: `ansible_local`** + +The Ansible Local provisioner allows you to provision the guest using [Ansible](http://ansible.com) playbooks by executing **`ansible-playbook` directly on the guest machine**. + +

    +

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

    +
    + +## Setup Requirements + +The main advantage of the Ansible Local provisioner in comparison to the [Ansible (remote) provisioner](/v2/provisioning/ansible.html) is that it does not require any additional software on your Vagrant host. + +On the other hand, [Ansible must obviously be installed](http://docs.ansible.com/intro_installation.html#installing-the-control-machine) on your guest machine(s). + +**Note:** By default, Vagrant will *try* to automatically install Ansible if it is not yet present on the guest machine (see the `install` option below for more details). + +## Usage + +This page only documents the specific parts of the `ansible_local` provisioner. General Ansible concepts like Playbook or Inventory are shortly explained in the [introduction to Ansible and Vagrant](/v2/provisioning/ansible_intro.html). + +The Ansible Local provisioner requires that all the Ansible Playbook files are available on the guest machine, at the location referred by the `provisioning_path` option. Usually these files are initially present on the host machine (as part of your Vagrant projet), and it is quite easy to share them with a Vagrant [Synced Folder](/v2/synced-folders/index.html). + +### Simplest Configuration + +To run Ansible from your Vagrant guest, the basic `Vagrantfile` configuration looks like: + +```ruby +Vagrant.configure(2) do |config| + + # + # Run Ansible from the Vagrant VM + # + config.vm.provision "ansible_local" do |ansible| + ansible.playbook = "playbook.yml" + end + +end +``` + +**Requirements:** + + - The `playbook.yml` file is stored in your Vagrant's project home directory. + + - The [default shared directory](/v2/synced-folders/basic_usage.html) is enabled (`.` → `/vagrant`). + +## Options + +This section lists the specific options for the Ansible Local provisioner. In addition to the options listed below, this provisioner supports the [common options for both Ansible provisioners](/v2/provisioning/ansible_common.html). + +- `install` (boolean) - Try to automatically install Ansible on the guest system. + + This option is enabled by default. + + Vagrant will to try to install (or upgrade) Ansible when one of these conditions are met: + + - Ansible is not installed (or cannot be found). + + - The `version` option is set to `"latest"`. + + - The current Ansible version does not correspond to the `version` option. + + **Attention:** There is no guarantee that this automated installation will replace a custom Ansible setup, that might be already present on the Vagrant box. + +- `provisioning_path` (string) - An absolute path on the guest machine where the Ansible files are stored. The `ansible-playbook` command is executed from this directory. + + The default value is `/vagrant`. + +- `tmp_path` (string) - An absolute path on the guest machine where temporary files are stored by the Ansible Local provisioner. + + The default value is `/tmp/vagrant-ansible` + +- `version` (string) - The expected Ansible version. + + This option is disabled by default. + + When an Ansible version is defined (e.g. `"1.8.2"`), the Ansible local provisioner will be executed only if Ansible is installed at the requested version. + + When this option is set to `"latest"`, no version check is applied. + + **Attention:** It is currently not possible to use this option to specify which version of Ansible must be automatically installed. With the `install` option enabled, the latest version packaged for the target operating system will always be installed. + +## Tips and Tricks + +### Ansible Parallel Execution from a Guest + +With the following configuration pattern, you can install and execute Ansible only on a single guest machine (the `"controller"`) to provision all your machines. + +```ruby +Vagrant.configure(2) do |config| + + config.vm.box = "ubuntu/trusty64" + + config.vm.define "node1" do |machine| + machine.vm.network "private_network", ip: "172.17.177.21" + end + + config.vm.define "node2" do |machine| + machine.vm.network "private_network", ip: "172.17.177.22" + end + + config.vm.define 'controller' do |machine| + machine.vm.network "private_network", ip: "172.17.177.11" + + machine.vm.provision :ansible_local do |ansible| + ansible.playbook = "example.yml" + ansible.verbose = true + ansible.install = true + ansible.limit = "all" # or only "nodes" group, etc. + ansible.inventory_path = "inventory" + end + end + +end +``` + +You need to create a static `inventory` file that corresponds to your `Vagrantfile` machine definitions: + +``` +controller ansible_connection=local +node1 ansible_ssh_host=172.17.177.21 ansible_ssh_private_key_file=/vagrant/.vagrant/machines/node1/virtualbox/private_key +node2 ansible_ssh_host=172.17.177.22 ansible_ssh_private_key_file=/vagrant/.vagrant/machines/node2/virtualbox/private_key + +[nodes] +node[1:2] +``` + +And finally, you also have to create an [`ansible.cfg` file](http://docs.ansible.com/intro_configuration.html#openssh-specific-settings) to fully disable SSH host key checking. More SSH configurations can be added to the `ssh_args` parameter (e.g. agent forwarding, etc.) + +``` +[defaults] +host_key_checking = no + +[ssh_connection] +ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes +``` diff --git a/website/docs/source/v2/provisioning/chef_common.html.md b/website/docs/source/v2/provisioning/chef_common.html.md index b5347d522..48d3f12cf 100644 --- a/website/docs/source/v2/provisioning/chef_common.html.md +++ b/website/docs/source/v2/provisioning/chef_common.html.md @@ -30,13 +30,18 @@ their purpose. - `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. + Omnibus installer, which varies between releases. This value has no effect on + Windows because Chef's omnibus installer lacks the option on Windows. - `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. +- `product` (string) - The name of the Chef product to install. The default + value is "chef", which corresponds to the Chef Client. You can also specify + "chefdk", which will install the Chef Development Kit. + +- `channel` (string) - The release channel from which to pull the Chef Client + or the Chef Development Kit. The default value is `"current"`. - `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 diff --git a/website/docs/source/v2/provisioning/chef_solo.html.md b/website/docs/source/v2/provisioning/chef_solo.html.md index a7841b90e..77d3825ab 100644 --- a/website/docs/source/v2/provisioning/chef_solo.html.md +++ b/website/docs/source/v2/provisioning/chef_solo.html.md @@ -36,12 +36,16 @@ available below this section. 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. +* `data_bags_path` (string or array) - A path where data bags are stored. By + default, no data bag path is set. Chef 12 or higher is required to use the + array option. Chef 11 and lower only accept a string value. * `environments_path` (string) - A path where environment definitions are located. By default, no environments folder is set. +* `nodes_path` (string or array) - A list of paths where node objects (in JSON format) are stored. By default, no + nodes path 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. diff --git a/website/docs/source/v2/provisioning/chef_zero.html.md b/website/docs/source/v2/provisioning/chef_zero.html.md index 4f35f362a..e409dc26a 100644 --- a/website/docs/source/v2/provisioning/chef_zero.html.md +++ b/website/docs/source/v2/provisioning/chef_zero.html.md @@ -35,12 +35,17 @@ available below this section. 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. +* `data_bags_path` (string or array) - A path where data bags are stored. By + default, no data bag path is set. Chef 12 or higher is required to use the + array option. Chef 11 and lower only accept a string value. * `environments_path` (string) - A path where environment definitions are located. By default, no environments folder is set. +* `nodes_path` (string or array) - A list of paths where node objects + (in JSON format) are stored. By default, no nodes path is set. This value is + required. + * `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. @@ -72,6 +77,7 @@ Vagrant.configure("2") do |config| # Specify the local paths where Chef data is stored chef.cookbooks_path = "cookbooks" chef.data_bags_path = "data_bags" + chef.nodes_path = "nodes" chef.roles_path = "roles" # Add a recipe diff --git a/website/docs/source/v2/provisioning/docker.html.md b/website/docs/source/v2/provisioning/docker.html.md index 0e03ec660..cb0d526fd 100644 --- a/website/docs/source/v2/provisioning/docker.html.md +++ b/website/docs/source/v2/provisioning/docker.html.md @@ -42,9 +42,6 @@ for you (if it isn't already installed). can also use the `pull_images` function. See the example below this section for more information. -* `version` (string) - The version of Docker to install. This defaults to - "latest" and will install the latest version of Docker. - In addition to the options that can be set, various functions are available and can be called to configure other aspects of the Docker provisioner. Most of these functions have examples in more detailed sections below. diff --git a/website/docs/source/v2/provisioning/file.html.md b/website/docs/source/v2/provisioning/file.html.md index ca65c8bb9..92de420d0 100644 --- a/website/docs/source/v2/provisioning/file.html.md +++ b/website/docs/source/v2/provisioning/file.html.md @@ -7,8 +7,8 @@ sidebar_current: "provisioning-file" **Provisioner name: `"file"`** -The file provisioner allows you to upload a file from the host machine to -the guest machine. +The file provisioner allows you to upload a file or directory from the host +machine to the guest machine. File provisioning is a simple way to, for example, replicate your local ~/.gitconfig to the vagrant user's home directory on the guest machine so @@ -21,19 +21,27 @@ new VM. config.vm.provision "file", source: "~/.gitconfig", destination: ".gitconfig" end -Note that, unlike with synced folders, files that are uploaded will not -be kept in sync. Continuing with the example above, if you make further -changes to your local ~/.gitconfig, they will not be immediately reflected -in the copy you uploaded to the guest machine. +Note that, unlike with synced folders, files or directories that are uploaded +will not be kept in sync. Continuing with the example above, if you make +further changes to your local ~/.gitconfig, they will not be immediately +reflected in the copy you uploaded to the guest machine. + +The file uploads by the file provisioner are done as the +_SSH or PowerShell user_. This is important since these users generally +don't have elevated privileges on their own. If you want to upload files to +locations that require elevated privileges, we recommend uploading them +to temporary locations and then using the +[shell provisioner](/v2/provisioning/shell.html) +to move them into place. ## Options The file provisioner takes only two options, both of which are required: -* `source` (string) - Is the local path of the file to be uploaded. +* `source` (string) - Is the local path of the file or directory to be + uploaded. * `destination` (string) - Is the remote path on the guest machine where - the file will be uploaded to. The file is uploaded as the SSH user over - SCP, so this location must be writable to that user. The SSH user can be + the source will be uploaded to. The file/folder is uploaded as the SSH user + over SCP, so this location must be writable to that user. The SSH user can be determined by running `vagrant ssh-config`, and defaults to "vagrant". - diff --git a/website/docs/source/v2/provisioning/puppet_agent.html.md b/website/docs/source/v2/provisioning/puppet_agent.html.md index 14d2b8a7c..26f43cf2a 100644 --- a/website/docs/source/v2/provisioning/puppet_agent.html.md +++ b/website/docs/source/v2/provisioning/puppet_agent.html.md @@ -26,6 +26,8 @@ the set of modules and manifests from there. The `puppet_server` provisioner takes various options. None are strictly required. They are listed below: +* `binary_path` (string) - Path on the guest to Puppet's `bin/` directory. + * `client_cert_path` (string) - Path to the client certificate for the node on your disk. This defaults to nothing, in which case a client cert won't be uploaded. diff --git a/website/docs/source/v2/provisioning/puppet_apply.html.md b/website/docs/source/v2/provisioning/puppet_apply.html.md index 89fe90195..b8f0a5f02 100644 --- a/website/docs/source/v2/provisioning/puppet_apply.html.md +++ b/website/docs/source/v2/provisioning/puppet_apply.html.md @@ -140,8 +140,9 @@ 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: +If you are using Puppet 4 or higher, you can proivision using +[Puppet Environments](https://docs.puppetlabs.com/puppet/latest/reference/environments.html) by specifying the name of the environment and the path on the +local disk to the environment files: ```ruby Vagrant.configure("2") do |config| @@ -152,6 +153,13 @@ Vagrant.configure("2") do |config| end ``` +The default manifest is the environment's `manifests` directory. +If the environment has an `environment.conf` the manifest path is parsed +from there. Relative paths are assumed to be relative to the directory of +the environment. If the manifest setting in `environment.conf` use +the Puppet variables `$codedir` or `$environment` they are resoled to +the parent directory of `environment_path` and `environment` respectively. + ## 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 37c70e4fd..323afa0b0 100644 --- a/website/docs/source/v2/provisioning/salt.html.md +++ b/website/docs/source/v2/provisioning/salt.html.md @@ -31,6 +31,7 @@ on a single minion, without a master: ## Use all the defaults: config.vm.provision :salt do |salt| + salt.masterless = true salt.minion_config = "salt/minion" salt.run_highstate = true @@ -56,16 +57,8 @@ on this machine. Not supported on Windows guest machines. * `install_type` (stable | git | daily | testing) - Whether to install from a distribution's stable package manager, git tree-ish, daily ppa, or testing repository. -Not supported on Windows guest machines. -* `install_args` (develop) - When performing a git install, -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 guest machines. - -* `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. +* `install_args` (develop) - When performing a git install, you can specify a branch, tag, or any treeish. Not supported on Windows. * `always_install` (boolean) - Installs salt binaries even if they are already detected, default `false` @@ -90,7 +83,7 @@ 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. +* `grains_config` (string) - Path to a custom salt grains file. On Windows, the minion needs `ipc_mode: tcp` set otherwise it will [fail to communicate](https://github.com/saltstack/salt/issues/22796) with the master. * `masterless` (boolean) - Calls state.highstate in local mode. Uses `minion_id` and `pillar_data` when provided. @@ -123,7 +116,7 @@ during provisioning. * `run_overstate` - (boolean) Executes `state.over` on vagrant up. Can be applied to the master only. This is superseded by orchestrate. Not supported on Windows guest machines. * `orchestrations` - (boolean) Executes `state.orchestrate` on -vagrant up. Can be applied to the master only. This is supersedes by run_overstate. Not supported on Windows guest machines. +vagrant up. Can be applied to the master only. This is superseded by run_overstate. Not supported on Windows guest machines. ## Output Control diff --git a/website/docs/source/v2/provisioning/shell.html.md b/website/docs/source/v2/provisioning/shell.html.md index ef4119668..eaee7779c 100644 --- a/website/docs/source/v2/provisioning/shell.html.md +++ b/website/docs/source/v2/provisioning/shell.html.md @@ -39,14 +39,19 @@ The remainder of the available options are optional: etc. as needed. You may also pass the arguments in using an array. In this case, Vagrant will handle quoting for you. +* `env` (hash) - List of key-value pairs to pass in as environment variables to + the script. Vagrant will handle quoting for environment variable values, but + the keys remain untouched. + * `binary` (boolean) - Vagrant automatically replaces Windows line endings with Unix line endings. If this is true, then Vagrant will not do this. By default this is "false". If the shell provisioner is communicating over WinRM, this defaults to "true". * `privileged` (boolean) - Specifies whether to execute the shell script - as a privileged user or not (`sudo`). By default this is "true". This has - no effect for Windows guests. + as a privileged user or not (`sudo`). By default this is "true". Windows + guests use a scheduled task to run as a true administrator without the + WinRM limitations. * `upload_path` (string) - Is the remote path where the shell script will be uploaded to. The script is uploaded as the SSH user over SCP, so this @@ -65,6 +70,11 @@ The remainder of the available options are optional: * `powershell_args` (string) - Extra arguments to pass to `PowerShell` if you're provisioning with PowerShell on Windows. +* `powershell_elevated_interactive` (boolean) - Run an elevated script in interactive mode + on Windows. By default this is "false". Must also be `privileged`. Be sure to + enable auto-login for Windows as the user must be logged in for interactive + mode to work. + ## Inline Scripts diff --git a/website/docs/source/v2/push/local-exec.html.md b/website/docs/source/v2/push/local-exec.html.md index 11ba906c8..2d267c4c2 100644 --- a/website/docs/source/v2/push/local-exec.html.md +++ b/website/docs/source/v2/push/local-exec.html.md @@ -27,6 +27,11 @@ options: 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). +- `args` (string or array) - Optional arguments to pass to the shell script when executing it + as a single string. These arguments must be written as if they were typed + directly on the command line, so be sure to escape characters, quote, + etc. as needed. You may also pass the arguments in using an array. In this + case, Vagrant will handle quoting for you. Please note - only one of the `script` and `inline` options may be specified in a single push definition. @@ -70,3 +75,7 @@ And then invoke the push with Vagrant: ```shell $ vagrant push ``` + +### Script Arguments + +Refer to [Shell Provisioner](/v2/provisioning/shell.html). diff --git a/website/docs/source/v2/synced-folders/nfs.html.md b/website/docs/source/v2/synced-folders/nfs.html.md index 962256512..d653751ba 100644 --- a/website/docs/source/v2/synced-folders/nfs.html.md +++ b/website/docs/source/v2/synced-folders/nfs.html.md @@ -152,12 +152,13 @@ Cmnd_Alias VAGRANT_EXPORTS_REMOVE = /usr/bin/sed -E -e /*/ d -ibak /etc/exports For Ubuntu Linux , sudoers should look like this: ``` -Cmnd_Alias VAGRANT_EXPORTS_ADD = /usr/bin/tee -a /etc/exports +ֻCmnd_Alias VAGRANT_EXPORTS_ADD = /usr/bin/tee -a /etc/exports +Cmnd_Alias VAGRANT_EXPORTS_COPY = /bin/cp /tmp/exports /etc/exports Cmnd_Alias VAGRANT_NFSD_CHECK = /etc/init.d/nfs-kernel-server status Cmnd_Alias VAGRANT_NFSD_START = /etc/init.d/nfs-kernel-server start Cmnd_Alias VAGRANT_NFSD_APPLY = /usr/sbin/exportfs -ar -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 +Cmnd_Alias VAGRANT_EXPORTS_REMOVE = /bin/sed -r -e * d -ibak /tmp/exports +%sudo ALL=(root) NOPASSWD: VAGRANT_EXPORTS_ADD, VAGRANT_NFSD_CHECK, VAGRANT_NFSD_START, VAGRANT_NFSD_APPLY, VAGRANT_EXPORTS_REMOVE, VAGRANT_EXPORTS_COPY ``` For Fedora Linux, sudoers might look like this (given your user diff --git a/website/docs/source/v2/vagrantfile/ssh_settings.html.md b/website/docs/source/v2/vagrantfile/ssh_settings.html.md index b07835b75..5db92e034 100644 --- a/website/docs/source/v2/vagrantfile/ssh_settings.html.md +++ b/website/docs/source/v2/vagrantfile/ssh_settings.html.md @@ -67,13 +67,23 @@ is enabled. Defaults to false.
    +`config.ssh.forward_env` - An array of host environment variables to forward to +the guest. If you are familiar with OpenSSH, this corresponds to the `SendEnv` +paramter. + +```ruby +config.ssh.forward_env = ["CUSTOM_VAR"] +``` + +
    + `config.ssh.insert_key` - If `true`, Vagrant will automatically insert -an keypair to use for SSH, replacing the default Vagrant's insecure key +a keypair to use for SSH, replacing Vagrant's default 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 +If you don't have to care about security in your project and want to keep using the default insecure key, set this to `false`.
    diff --git a/website/docs/source/v2/vagrantfile/winrm_settings.html.md b/website/docs/source/v2/vagrantfile/winrm_settings.html.md index add316a40..db1ced243 100644 --- a/website/docs/source/v2/vagrantfile/winrm_settings.html.md +++ b/website/docs/source/v2/vagrantfile/winrm_settings.html.md @@ -46,10 +46,16 @@ to use port 4567 to talk to the guest if there is no other option.
    +`config.winrm.execution_time_limit` - The maximum duration that a WinRM +task can execute for. This defaults to two hours. The format of this value +must be in this [Microsoft-documented format](https://msdn.microsoft.com/en-us/library/aa382678.aspx). + +
    + 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 +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. @@ -57,4 +63,4 @@ 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/boxes.html.md b/website/docs/source/v2/virtualbox/boxes.html.md index 6026369c0..3c8a8330a 100644 --- a/website/docs/source/v2/virtualbox/boxes.html.md +++ b/website/docs/source/v2/virtualbox/boxes.html.md @@ -159,4 +159,4 @@ of the NAT device without colons. When bringing up a VirtualBox backed machine, Vagrant [imports](http://www.virtualbox.org/manual/ch08.html#vboxmanage-import) -the first "ovf" file found in the box contents. +the "box.ovf" file found in the box contents. diff --git a/website/docs/source/v2/virtualbox/networking.html.md b/website/docs/source/v2/virtualbox/networking.html.md index 55e7d4c4c..5811373de 100644 --- a/website/docs/source/v2/virtualbox/networking.html.md +++ b/website/docs/source/v2/virtualbox/networking.html.md @@ -36,8 +36,8 @@ 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__` +You can specify a specific NIC type for the created network interface +by using the `nic_type` 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 @@ -48,6 +48,6 @@ Example: ```ruby Vagrant.configure("2") do |config| config.vm.network "private_network", ip: "192.168.50.4", - nictype: "virtio" + nic_type: "virtio" end ``` diff --git a/website/www/Makefile b/website/www/Makefile new file mode 100644 index 000000000..63bb4cab1 --- /dev/null +++ b/website/www/Makefile @@ -0,0 +1,10 @@ +all: build + +init: + bundle + +dev: init + bundle exec middleman server + +build: init + bundle exec middleman build \ No newline at end of file diff --git a/website/www/README.md b/website/www/README.md index 814da3877..3645fa52c 100644 --- a/website/www/README.md +++ b/website/www/README.md @@ -14,13 +14,7 @@ requests like any normal GitHub project, and we'll merge it in. ## Running the Site Locally -Running the site locally is simple. Clone this repo and run the following -commands: - -``` -$ bundle -$ bundle exec middleman server -``` +Running the site locally is simple. Clone this repo and run `make dev`. Then open up `localhost:4567`. Note that some URLs you may need to append ".html" to make them work (in the navigation and such). diff --git a/website/www/Vagrantfile b/website/www/Vagrantfile index 2d8d8c9b4..cb40d506b 100644 --- a/website/www/Vagrantfile +++ b/website/www/Vagrantfile @@ -6,8 +6,9 @@ sudo apt-get -y update sudo apt-get -y install curl sudo su -l -c 'gpg --keyserver hkp://keys.gnupg.net --recv-keys D39DC0E3' vagrant sudo su -l -c 'curl -L https://get.rvm.io | bash -s stable --auto-dotfiles' vagrant -sudo su -l -c 'rvm install 2.0.0' vagrant -sudo su -l -c 'rvm --default use 2.0.0' vagrant +sudo su -l -c 'rvm install 2.2.2' vagrant +sudo su -l -c 'rvm --default use 2.2.2' vagrant +sudo su -l -c 'gem install bundler' vagrant sudo su -l -c 'cd /vagrant && bundle install --jobs 2' vagrant SCRIPT diff --git a/website/www/source/images/icons/icon_debian.png b/website/www/source/images/icons/icon_debian.png index 0e92d4fcc..3c096cecb 100644 Binary files a/website/www/source/images/icons/icon_debian.png and b/website/www/source/images/icons/icon_debian.png differ diff --git a/website/www/source/layouts/layout.erb b/website/www/source/layouts/layout.erb index f0d2538e5..14c0d307d 100644 --- a/website/www/source/layouts/layout.erb +++ b/website/www/source/layouts/layout.erb @@ -52,6 +52,7 @@
    - <%= partial "layouts/mobile_nav" %> - <%= yield %> @@ -75,6 +74,13 @@
  • Documentation
  • About
  • Support
  • + <% if current_page.url != "/" %> + <% relative_path = current_page.path.match(/.*\//).to_s + file = current_page.source_file.split("/").last + github_link = "https://github.com/mitchellh/vagrant/blob/master/website/www/source/#{relative_path}#{file}" + %> +
  • Edit this page
  • + <% end %>
  • Download
  • @@ -94,9 +100,11 @@
    - +
    + +