vagrant destroy -f returns nonzero if a VM is destroyed?

Created on 13 Jul 2018  ·  4Comments  ·  Source: hashicorp/vagrant

Vagrant version

Vagrant 2.1.2

Host operating system

macOS 10.13.5

Guest operating system

Ubuntu 14.04 (but it shouldn't matter)

Vagrantfile

here's the vagrantfile I used but you probably won't need it

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|

  # Vagrant 1.7.0+ removes the insecure_private_key by default
  # and substitutes a dynamically generated SSH key for each box.
  # Unfortunately this breaks Ansible provisioning with Vagrant,
  # so the key insertion feature should be disabled.
  config.ssh.insert_key = false

  # The staging hosts are just like production but allow non-tor access
  # for the web interfaces and ssh.
  config.vm.define 'mon-staging', autostart: false do |staging|
    if ENV['SECUREDROP_SSH_OVER_TOR']
      config.ssh.host = find_ssh_aths("mon-ssh-aths")
      config.ssh.proxy_command = tor_ssh_proxy_command
      config.ssh.port = 22
    elsif ARGV[0] == "ssh"
      config.ssh.host = "10.0.1.3"
      config.ssh.port = 22
    end
    staging.vm.hostname = "mon-staging"
    staging.vm.box = "bento/ubuntu-14.04"
    staging.vm.network "private_network", ip: "10.0.1.3"
    staging.vm.synced_folder './', '/vagrant', disabled: true
  end

  config.vm.define 'app-staging', autostart: false do |staging|
    if ENV['SECUREDROP_SSH_OVER_TOR']
      config.ssh.host = find_ssh_aths("app-ssh-aths")
      config.ssh.proxy_command = tor_ssh_proxy_command
      config.ssh.port = 22
    elsif ARGV[0] == "ssh"
      config.ssh.host = "10.0.1.2"
      config.ssh.port = 22
    end
    staging.vm.hostname = "app-staging"
    staging.vm.box = "bento/ubuntu-14.04"
    staging.vm.network "private_network", ip: "10.0.1.2"
    staging.vm.synced_folder './', '/vagrant', disabled: true
    staging.vm.provider "virtualbox" do |v|
      v.memory = 1024
    end
    staging.vm.provider "libvirt" do |lv, override|
      lv.memory = 1024
    end
    staging.vm.provision "ansible" do |ansible|
      ansible.playbook = "install_files/ansible-base/securedrop-staging.yml"
      ansible.inventory_path = "install_files/ansible-base/inventory-staging"
      ansible.verbose = 'v'
      # Taken from the parallel execution tips and tricks
      # https://docs.vagrantup.com/v2/provisioning/ansible.html
      ansible.limit = 'all,localhost'
      ansible.raw_arguments = Shellwords.shellsplit(ENV['ANSIBLE_ARGS']) if ENV['ANSIBLE_ARGS']
    end
  end

  # The prod hosts are just like production but are virtualized.
  # All access to SSH and the web interfaces is only over Tor.
  config.vm.define 'mon-prod', autostart: false do |prod|
    if ENV['SECUREDROP_SSH_OVER_TOR']
      config.ssh.host = find_ssh_aths("mon-ssh-aths")
      config.ssh.proxy_command = tor_ssh_proxy_command
      config.ssh.port = 22
    end
    prod.vm.hostname = "mon-prod"
    prod.vm.box = "bento/ubuntu-14.04"
    prod.vm.network "private_network", ip: "10.0.1.5", virtualbox__intnet: internal_network_name
    prod.vm.synced_folder './', '/vagrant', disabled: true
  end

  config.vm.define 'app-prod', autostart: false do |prod|
    if ENV['SECUREDROP_SSH_OVER_TOR']
      config.ssh.host = find_ssh_aths("app-ssh-aths")
      config.ssh.proxy_command = tor_ssh_proxy_command
      config.ssh.port = 22
    end
    prod.vm.hostname = "app-prod"
    prod.vm.box = "bento/ubuntu-14.04"
    prod.vm.network "private_network", ip: "10.0.1.4", virtualbox__intnet: internal_network_name
    prod.vm.synced_folder './', '/vagrant', disabled: true
    prod.vm.provider "virtualbox" do |v|
      v.memory = 1024
    end
    prod.vm.provision "ansible" do |ansible|
      ansible.playbook = "install_files/ansible-base/securedrop-prod.yml"
      ansible.verbose = 'v'
      # the production playbook verifies that staging default values are not
      # used will need to skip the this role to run in Vagrant
      ansible.raw_arguments = Shellwords.shellsplit(ENV['ANSIBLE_ARGS']) if ENV['ANSIBLE_ARGS']
      # Taken from the parallel execution tips and tricks
      # https://docs.vagrantup.com/v2/provisioning/ansible.html
      ansible.limit = 'all,localhost'
      ansible.groups = {
        'securedrop_application_server' => %w(app-prod),
        'securedrop_monitor_server' => %w(mon-prod),
        'securedrop' => %w(app-prod mon-prod)
      }
    end
  end

end


# Get .onion URL for connecting to instances over Tor.
# The Ansible playbooks fetch these values back to the
# Admin Workstation (localhost) so they can be manually
# added to the inventory file. Possible values for filename
# are "app-ssh-aths" and "mon-ssh-aths".
def find_ssh_aths(filename)
  repo_root = File.expand_path(File.dirname(__FILE__))
  aths_file = File.join(repo_root, "install_files", "ansible-base", filename)
  if FileTest.file?(aths_file)
    File.open(aths_file).each do |line|
      # Take second value for URL; format for the ATHS file is:
      # /^HidServAuth \w{16}.onion \w{22} # client: admin$/
      return line.split()[1]
    end
  else
    puts "Failed to find ATHS file: #{filename}"
    puts "Cannot connect via SSH."
    exit(1)
  end
end

# Build proxy command for connecting to prod instances over Tor.
def tor_ssh_proxy_command
   def command?(command)
     system("which #{command} > /dev/null 2>&1")
   end
  if command?("nc")
    base_cmd = "nc -x"
  else
    puts "Failed to build proxy command for SSH over Tor."
    puts "Install or 'netcat-openbsd'."
    exit(1)
  end
  return "#{base_cmd} 127.0.0.1:9050 %h %p"
end

# Create a unique name for the VirtualBox internal network,
# based on the directory name of the repo. This is to avoid
# accidental IP collisions when running multiple instances
# of the staging or prod environment concurrently.
def internal_network_name
  repo_root = File.expand_path(File.dirname(__FILE__))
  return File.basename(repo_root)
end

Debug output

https://gist.github.com/redshiftzero/bbfcf5c16508ffb332c1a4e043f14685

Expected behavior

Returns 0

Actual behavior

Returns 2

Steps to reproduce

  1. have multiple VMs defined in your Vagrantfile
  2. vagrant up one of them
  3. vagrant destroy -f
  4. Observe non-zero exit code

Is that the right exit code? seems like if everything was destroyed cleanly it should exit 0, feel free to close this ticket if exiting non-zero is the expected behavior

References

Related to #9137

Most helpful comment

I'd 2nd that this behaviour is unusual. Is there any chance you'd reconsider changing it?
See e.g. the behaviour of rm, cp.
For people used to these commands -f signifies "I just want it done", and as @conorsch mentions, this makes the command easier to use as part of a script, as it's idempotent.
Forcing scripts to append || true is also suboptimal because it'll mask other failure causes (couldn't find `Vagrantfile at all?, VM provider failed to actually stop a running VM?).

All 4 comments

Hi @redshiftzero - Yes, this is expected behavior. I suspect you have more than just 1 vm defined in your Vagrant environment. If you do a general vagrant destroy -f and not all vms are able to be destroyed (like perhaps they are not created, or for any other reason), Vagrant will return 2 and not 0. If you specify a guest or guests with the command like vagrant destroy myguest -f and it is able to successfully destroy all of the target guests, it will return 0.

An example below...I've omitted some irrelevant output with ...'s:

brian@localghost:vagrant-sandbox % be vagrant up bork                                                                    ±[●●][master]
Bringing machine 'bork' up with 'virtualbox' provider...
.....
.....
.....
brian@localghost:vagrant-sandbox % be vagrant destroy -f                                                                 ±[●●][master]
==> suse: VM not created. Moving on...
==> dockerwindows: VM not created. Moving on...
==> ubuntu17: VM not created. Moving on...
==> debian: VM not created. Moving on...
==> gentoo: VM not created. Moving on...
==> openbsd: VM not created. Moving on...
==> arch: VM not created. Moving on...
==> macos: VM not created. Moving on...
==> windows: VM not created. Moving on...
==> windoze: VM not created. Moving on...
==> salt: VM not created. Moving on...
==> ansible: VM not created. Moving on...
==> centos: VM not created. Moving on...
==> puppet: VM not created. Moving on...
==> chef: VM not created. Moving on...
==> docker-3: The container hasn't been created yet.
==> docker-2: The container hasn't been created yet.
==> docker-1: The container hasn't been created yet.
==> vbox: VM not created. Moving on...
==> bork: Forcing shutdown of VM...
==> bork: Destroying VM and associated drives...
brian@localghost:vagrant-sandbox % echo $?                                                                               ±[●●][master]
2
brian@localghost:vagrant-sandbox % be vagrant up bork                                                                    ±[●●][master]
Bringing machine 'bork' up with 'virtualbox' provider...
.....
......
.....
....
brian@localghost:vagrant-sandbox % be vagrant destroy bork -f                                                            ±[●●][master]

==> bork: Forcing shutdown of VM...
==> bork: Destroying VM and associated drives...
brian@localghost:vagrant-sandbox % echo $?                                                                               ±[●●][master]
0
brian@localghost:vagrant-sandbox %

I'll go ahead and close this ticket. But if you have any other issues or questions feel free to comment or open a new issue, thank you!

Thanks for the clarification, @briancain! That's not the behavior I would have expected; I'd expect a more idempotent command, in that vagrant destroy -f (with no VM explicitly targeted) returns 0 if the end state results in 0 VMs. Still, with the information you provided, at least we're on the same page about what we're observing.

For wrapper scripts to Vagrant, we'll append || true to any vagrant destroy -f commands, and that'll suit our needs.

I'd 2nd that this behaviour is unusual. Is there any chance you'd reconsider changing it?
See e.g. the behaviour of rm, cp.
For people used to these commands -f signifies "I just want it done", and as @conorsch mentions, this makes the command easier to use as part of a script, as it's idempotent.
Forcing scripts to append || true is also suboptimal because it'll mask other failure causes (couldn't find `Vagrantfile at all?, VM provider failed to actually stop a running VM?).

I'm going to lock this issue because it has been closed for _30 days_ ⏳. This helps our maintainers find and focus on the active issues.

If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

Was this page helpful?
0 / 5 - 0 ratings