Merge pull request #6374 from mitchellh/f-snapshot

Snapshot Support
This commit is contained in:
Mitchell Hashimoto 2015-10-08 10:44:18 -04:00
commit 51f5b9e036
28 changed files with 1029 additions and 1 deletions

View File

@ -20,6 +20,7 @@ module Vagrant
autoload :HandleBox, "vagrant/action/builtin/handle_box" autoload :HandleBox, "vagrant/action/builtin/handle_box"
autoload :HandleBoxUrl, "vagrant/action/builtin/handle_box_url" autoload :HandleBoxUrl, "vagrant/action/builtin/handle_box_url"
autoload :HandleForwardedPortCollisions, "vagrant/action/builtin/handle_forwarded_port_collisions" autoload :HandleForwardedPortCollisions, "vagrant/action/builtin/handle_forwarded_port_collisions"
autoload :IsEnvSet, "vagrant/action/builtin/is_env_set"
autoload :IsState, "vagrant/action/builtin/is_state" autoload :IsState, "vagrant/action/builtin/is_state"
autoload :Lock, "vagrant/action/builtin/lock" autoload :Lock, "vagrant/action/builtin/lock"
autoload :Message, "vagrant/action/builtin/message" autoload :Message, "vagrant/action/builtin/message"

View File

@ -0,0 +1,22 @@
module Vagrant
module Action
module Builtin
# This middleware is meant to be used with Call and can check if
# a variable in env is set.
class IsEnvSet
def initialize(app, env, key, **opts)
@app = app
@logger = Log4r::Logger.new("vagrant::action::builtin::is_env_set")
@key = key
end
def call(env)
@logger.debug("Checking if env is set: '#{@key}'")
env[:result] = !!env[@key]
@logger.debug(" - Result: #{env[:result].inspect}")
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,35 @@
require 'optparse'
module VagrantPlugins
module CommandSnapshot
module Command
class Delete < Vagrant.plugin("2", :command)
def execute
options = {}
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant snapshot delete [options] [vm-name] <name>"
o.separator ""
o.separator "Delete a snapshot taken previously with snapshot save."
end
# Parse the options
argv = parse_options(opts)
return if !argv
if argv.empty? || argv.length > 2
raise Vagrant::Errors::CLIInvalidUsage,
help: opts.help.chomp
end
name = argv.pop
with_target_vms(argv) do |vm|
vm.action(:snapshot_delete, snapshot_name: name)
end
# Success, exit status 0
0
end
end
end
end
end

View File

@ -0,0 +1,47 @@
require 'optparse'
module VagrantPlugins
module CommandSnapshot
module Command
class List < Vagrant.plugin("2", :command)
def execute
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant snapshot list [options] [vm-name]"
o.separator ""
o.separator "List all snapshots taken for a machine."
end
# Parse the options
argv = parse_options(opts)
return if !argv
with_target_vms(argv) do |vm|
if !vm.id
vm.ui.info(I18n.t("vagrant.commands.common.vm_not_created"))
next
end
if !vm.provider.capability?(:snapshot_list)
vm.ui.info(I18n.t("vagrant.commands.snapshot.not_supported"))
next
end
snapshots = vm.provider.capability(:snapshot_list)
if snapshots.empty?
vm.ui.output(I18n.t("vagrant.actions.vm.snapshot.list_none"))
vm.ui.detail(I18n.t("vagrant.actions.vm.snapshot.list_none_detail"))
next
end
snapshots.each do |snapshot|
vm.ui.output(snapshot, prefix: false)
end
end
# Success, exit status 0
0
end
end
end
end
end

View File

@ -0,0 +1,28 @@
require 'json'
require 'optparse'
require_relative "push_shared"
module VagrantPlugins
module CommandSnapshot
module Command
class Pop < Vagrant.plugin("2", :command)
include PushShared
def execute
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant snapshot pop [options] [vm-name]"
o.separator ""
o.separator "Restore state that was pushed with `vagrant snapshot push`."
end
# Parse the options
argv = parse_options(opts)
return if !argv
return shared_exec(argv, method(:pop))
end
end
end
end
end

View File

@ -0,0 +1,33 @@
require 'json'
require 'optparse'
require_relative "push_shared"
module VagrantPlugins
module CommandSnapshot
module Command
class Push < Vagrant.plugin("2", :command)
include PushShared
def execute
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant snapshot push [options] [vm-name]"
o.separator ""
o.separator "Take a snapshot of the current state of the machine and 'push'"
o.separator "it onto the stack of states. You can use `vagrant snapshot pop`"
o.separator "to restore back to this state at any time."
o.separator ""
o.separator "If you use `vagrant snapshot save` or restore at any point after"
o.separator "a push, pop will still bring you back to this pushed state."
end
# Parse the options
argv = parse_options(opts)
return if !argv
return shared_exec(argv, method(:push))
end
end
end
end
end

View File

@ -0,0 +1,57 @@
require 'json'
module VagrantPlugins
module CommandSnapshot
module Command
module PushShared
def shared_exec(argv, m)
with_target_vms(argv) do |vm|
if !vm.id
vm.ui.info("Not created. Cannot push snapshot state.")
next
end
vm.env.lock("machine-snapshot-stack") do
m.call(vm)
end
end
# Success, exit with 0
0
end
def push(machine)
snapshot_name = "push_#{Time.now.to_i}_#{rand(10000)}"
# Save the snapshot. This will raise an exception if it fails.
machine.action(:snapshot_save, snapshot_name: snapshot_name)
end
def pop(machine)
# By reverse sorting, we should be able to find the first
# pushed snapshot.
name = nil
snapshots = machine.provider.capability(:snapshot_list)
snapshots.sort.reverse.each do |snapshot|
if snapshot =~ /^push_\d+_\d+$/
name = snapshot
break
end
end
# If no snapshot was found, we never pushed
if !name
machine.ui.info(I18n.t("vagrant.commands.snapshot.no_push_snapshot"))
return
end
# Restore the snapshot and tell the provider to delete it as well.
machine.action(
:snapshot_restore,
snapshot_name: name,
snapshot_delete: true)
end
end
end
end
end

View File

@ -0,0 +1,35 @@
require 'optparse'
module VagrantPlugins
module CommandSnapshot
module Command
class Restore < Vagrant.plugin("2", :command)
def execute
options = {}
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant snapshot restore [options] [vm-name] <name>"
o.separator ""
o.separator "Restore a snapshot taken previously with snapshot save."
end
# Parse the options
argv = parse_options(opts)
return if !argv
if argv.empty? || argv.length > 2
raise Vagrant::Errors::CLIInvalidUsage,
help: opts.help.chomp
end
name = argv.pop
with_target_vms(argv) do |vm|
vm.action(:snapshot_restore, snapshot_name: name)
end
# Success, exit status 0
0
end
end
end
end
end

View File

@ -0,0 +1,89 @@
require 'optparse'
module VagrantPlugins
module CommandSnapshot
module Command
class Root < Vagrant.plugin("2", :command)
def self.synopsis
"manages snapshots: saving, restoring, etc."
end
def initialize(argv, env)
super
@main_args, @sub_command, @sub_args = split_main_and_subcommand(argv)
@subcommands = Vagrant::Registry.new
@subcommands.register(:save) do
require_relative "save"
Save
end
@subcommands.register(:restore) do
require_relative "restore"
Restore
end
@subcommands.register(:delete) do
require_relative "delete"
Delete
end
@subcommands.register(:list) do
require_relative "list"
List
end
@subcommands.register(:push) do
require_relative "push"
Push
end
@subcommands.register(:pop) do
require_relative "pop"
Pop
end
end
def execute
if @main_args.include?("-h") || @main_args.include?("--help")
# Print the help for all the commands.
return help
end
# If we reached this far then we must have a subcommand. If not,
# then we also just print the help and exit.
command_class = @subcommands.get(@sub_command.to_sym) if @sub_command
return help if !command_class || !@sub_command
@logger.debug("Invoking command class: #{command_class} #{@sub_args.inspect}")
# Initialize and execute the command class
command_class.new(@sub_args, @env).execute
end
# Prints the help out for this command
def help
opts = OptionParser.new do |opts|
opts.banner = "Usage: vagrant snapshot <subcommand> [<args>]"
opts.separator ""
opts.separator "Available subcommands:"
# Add the available subcommands as separators in order to print them
# out as well.
keys = []
@subcommands.each { |key, value| keys << key.to_s }
keys.sort.each do |key|
opts.separator " #{key}"
end
opts.separator ""
opts.separator "For help on any individual subcommand run `vagrant snapshot <subcommand> -h`"
end
@env.ui.info(opts.help, prefix: false)
end
end
end
end
end

View File

@ -0,0 +1,40 @@
require 'optparse'
module VagrantPlugins
module CommandSnapshot
module Command
class Save < Vagrant.plugin("2", :command)
def execute
options = {}
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant snapshot save [options] [vm-name] <name>"
o.separator ""
o.separator "Take a snapshot of the current state of the machine. The snapshot"
o.separator "can be restored via `vagrant snapshot restore` at any point in the"
o.separator "future to get back to this exact machine state."
o.separator ""
o.separator "Snapshots are useful for experimenting in a machine and being able"
o.separator "to rollback quickly."
end
# Parse the options
argv = parse_options(opts)
return if !argv
if argv.empty? || argv.length > 2
raise Vagrant::Errors::CLIInvalidUsage,
help: opts.help.chomp
end
name = argv.pop
with_target_vms(argv) do |vm|
vm.action(:snapshot_save, snapshot_name: name)
end
# Success, exit status 0
0
end
end
end
end
end

View File

@ -0,0 +1,15 @@
require "vagrant"
module VagrantPlugins
module CommandSnapshot
class Plugin < Vagrant.plugin("2")
name "snapshot command"
description "The `snapshot` command gives you a way to manage snapshots."
command("snapshot") do
require_relative "command/root"
Command::Root
end
end
end
end

View File

@ -42,6 +42,9 @@ module VagrantPlugins
autoload :SaneDefaults, File.expand_path("../action/sane_defaults", __FILE__) autoload :SaneDefaults, File.expand_path("../action/sane_defaults", __FILE__)
autoload :SetName, File.expand_path("../action/set_name", __FILE__) autoload :SetName, File.expand_path("../action/set_name", __FILE__)
autoload :SetupPackageFiles, File.expand_path("../action/setup_package_files", __FILE__) autoload :SetupPackageFiles, File.expand_path("../action/setup_package_files", __FILE__)
autoload :SnapshotDelete, File.expand_path("../action/snapshot_delete", __FILE__)
autoload :SnapshotRestore, File.expand_path("../action/snapshot_restore", __FILE__)
autoload :SnapshotSave, File.expand_path("../action/snapshot_save", __FILE__)
autoload :Suspend, File.expand_path("../action/suspend", __FILE__) autoload :Suspend, File.expand_path("../action/suspend", __FILE__)
# Include the built-in modules so that we can use them as top-level # Include the built-in modules so that we can use them as top-level
@ -222,6 +225,59 @@ module VagrantPlugins
end end
end end
def self.action_snapshot_delete
Vagrant::Action::Builder.new.tap do |b|
b.use CheckVirtualbox
b.use Call, Created do |env, b2|
if env[:result]
b2.use SnapshotDelete
else
b2.use MessageNotCreated
end
end
end
end
# This is the action that is primarily responsible for saving a snapshot
def self.action_snapshot_restore
Vagrant::Action::Builder.new.tap do |b|
b.use CheckVirtualbox
b.use Call, Created do |env, b2|
if !env[:result]
b2.use MessageNotCreated
next
end
b2.use CheckAccessible
b2.use EnvSet, force_halt: true
b2.use action_halt
b2.use SnapshotRestore
b2.use Call, IsEnvSet, :snapshot_delete do |env2, b3|
if env2[:result]
b3.use action_snapshot_delete
end
end
b2.use action_start
end
end
end
# This is the action that is primarily responsible for saving a snapshot
def self.action_snapshot_save
Vagrant::Action::Builder.new.tap do |b|
b.use CheckVirtualbox
b.use Call, Created do |env, b2|
if env[:result]
b2.use SnapshotSave
else
b2.use MessageNotCreated
end
end
end
end
# This is the action that will exec into an SSH shell. # This is the action that will exec into an SSH shell.
def self.action_ssh def self.action_ssh
Vagrant::Action::Builder.new.tap do |b| Vagrant::Action::Builder.new.tap do |b|

View File

@ -0,0 +1,32 @@
module VagrantPlugins
module ProviderVirtualBox
module Action
class SnapshotDelete
def initialize(app, env)
@app = app
end
def call(env)
env[:ui].info(I18n.t(
"vagrant.actions.vm.snapshot.deleting",
name: env[:snapshot_name]))
env[:machine].provider.driver.delete_snapshot(
env[:machine].id, env[:snapshot_name]) do |progress|
env[:ui].clear_line
env[:ui].report_progress(progress, 100, false)
end
# Clear the line one last time since the progress meter doesn't disappear
# immediately.
env[:ui].clear_line
env[:ui].success(I18n.t(
"vagrant.actions.vm.snapshot.deleted",
name: env[:snapshot_name]))
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,28 @@
module VagrantPlugins
module ProviderVirtualBox
module Action
class SnapshotRestore
def initialize(app, env)
@app = app
end
def call(env)
env[:ui].info(I18n.t(
"vagrant.actions.vm.snapshot.restoring",
name: env[:snapshot_name]))
env[:machine].provider.driver.restore_snapshot(
env[:machine].id, env[:snapshot_name]) do |progress|
env[:ui].clear_line
env[:ui].report_progress(progress, 100, false)
end
# Clear the line one last time since the progress meter doesn't disappear
# immediately.
env[:ui].clear_line
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,25 @@
module VagrantPlugins
module ProviderVirtualBox
module Action
class SnapshotSave
def initialize(app, env)
@app = app
end
def call(env)
env[:ui].info(I18n.t(
"vagrant.actions.vm.snapshot.saving",
name: env[:snapshot_name]))
env[:machine].provider.driver.create_snapshot(
env[:machine].id, env[:snapshot_name])
env[:ui].success(I18n.t(
"vagrant.actions.vm.snapshot.saved",
name: env[:snapshot_name]))
@app.call(env)
end
end
end
end
end

View File

@ -22,6 +22,13 @@ module VagrantPlugins
def self.nic_mac_addresses(machine) def self.nic_mac_addresses(machine)
machine.provider.driver.read_mac_addresses machine.provider.driver.read_mac_addresses
end end
# Returns a list of the snapshots that are taken on this machine.
#
# @return [Array<String>] Snapshot Name
def self.snapshot_list(machine)
machine.provider.driver.list_snapshots(machine.id)
end
end end
end end
end end

View File

@ -386,7 +386,8 @@ module VagrantPlugins
if errored if errored
raise Vagrant::Errors::VBoxManageError, raise Vagrant::Errors::VBoxManageError,
command: command.inspect, command: command.inspect,
stderr: r.stderr stderr: r.stderr,
stdout: r.stdout
end end
end end

View File

@ -89,6 +89,7 @@ module VagrantPlugins
:create_host_only_network, :create_host_only_network,
:create_snapshot, :create_snapshot,
:delete, :delete,
:delete_snapshot,
:delete_unused_host_only_networks, :delete_unused_host_only_networks,
:discard_saved_state, :discard_saved_state,
:enable_adapters, :enable_adapters,
@ -97,6 +98,7 @@ module VagrantPlugins
:forward_ports, :forward_ports,
:halt, :halt,
:import, :import,
:list_snapshots,
:read_forwarded_ports, :read_forwarded_ports,
:read_bridged_interfaces, :read_bridged_interfaces,
:read_dhcp_servers, :read_dhcp_servers,
@ -113,6 +115,7 @@ module VagrantPlugins
:read_vms, :read_vms,
:reconfig_host_only, :reconfig_host_only,
:remove_dhcp_server, :remove_dhcp_server,
:restore_snapshot,
:resume, :resume,
:set_mac_address, :set_mac_address,
:set_name, :set_name,

View File

@ -608,6 +608,85 @@ module VagrantPlugins
execute("showvminfo", uuid) execute("showvminfo", uuid)
return true return true
end end
def create_snapshot(machine_id, snapshot_name)
execute("snapshot", machine_id, "take", snapshot_name)
end
def delete_snapshot(machine_id, snapshot_name)
# Start with 0%
last = 0
total = ""
yield 0 if block_given?
# Snapshot and report the % progress
execute("snapshot", machine_id, "delete", snapshot_name) do |type, data|
if type == :stderr
# Append the data so we can see the full view
total << data.gsub("\r", "")
# Break up the lines. We can't get the progress until we see an "OK"
lines = total.split("\n")
# The progress of the import will be in the last line. Do a greedy
# regular expression to find what we're looking for.
match = /.+(\d{2})%/.match(lines.last)
if match
current = match[1].to_i
if current > last
last = current
yield current if block_given?
end
end
end
end
end
def list_snapshots(machine_id)
output = execute(
"snapshot", machine_id, "list", "--machinereadable",
retryable: true)
result = []
output.split("\n").each do |line|
if line =~ /^SnapshotName.*?="(.+?)"$/i
result << $1.to_s
end
end
result.sort
rescue Vagrant::Errors::VBoxManageError => e
return [] if e.extra_data[:stdout].include?("does not have")
raise
end
def restore_snapshot(machine_id, snapshot_name)
# Start with 0%
last = 0
total = ""
yield 0 if block_given?
execute("snapshot", machine_id, "restore", snapshot_name) do |type, data|
if type == :stderr
# Append the data so we can see the full view
total << data.gsub("\r", "")
# Break up the lines. We can't get the progress until we see an "OK"
lines = total.split("\n")
# The progress of the import will be in the last line. Do a greedy
# regular expression to find what we're looking for.
match = /.+(\d{2})%/.match(lines.last)
if match
current = match[1].to_i
if current > last
last = current
yield current if block_given?
end
end
end
end
end
end end
end end
end end

View File

@ -90,6 +90,81 @@ module VagrantPlugins
execute("snapshot", machine_id, "take", snapshot_name) execute("snapshot", machine_id, "take", snapshot_name)
end end
def delete_snapshot(machine_id, snapshot_name)
# Start with 0%
last = 0
total = ""
yield 0 if block_given?
# Snapshot and report the % progress
execute("snapshot", machine_id, "delete", snapshot_name) do |type, data|
if type == :stderr
# Append the data so we can see the full view
total << data.gsub("\r", "")
# Break up the lines. We can't get the progress until we see an "OK"
lines = total.split("\n")
# The progress of the import will be in the last line. Do a greedy
# regular expression to find what we're looking for.
match = /.+(\d{2})%/.match(lines.last)
if match
current = match[1].to_i
if current > last
last = current
yield current if block_given?
end
end
end
end
end
def list_snapshots(machine_id)
output = execute(
"snapshot", machine_id, "list", "--machinereadable",
retryable: true)
result = []
output.split("\n").each do |line|
if line =~ /^SnapshotName.*?="(.+?)"$/i
result << $1.to_s
end
end
result.sort
rescue Vagrant::Errors::VBoxManageError => e
return [] if e.extra_data[:stdout].include?("does not have")
raise
end
def restore_snapshot(machine_id, snapshot_name)
# Start with 0%
last = 0
total = ""
yield 0 if block_given?
execute("snapshot", machine_id, "restore", snapshot_name) do |type, data|
if type == :stderr
# Append the data so we can see the full view
total << data.gsub("\r", "")
# Break up the lines. We can't get the progress until we see an "OK"
lines = total.split("\n")
# The progress of the import will be in the last line. Do a greedy
# regular expression to find what we're looking for.
match = /.+(\d{2})%/.match(lines.last)
if match
current = match[1].to_i
if current > last
last = current
yield current if block_given?
end
end
end
end
end
def delete def delete
execute("unregistervm", @uuid, "--delete") execute("unregistervm", @uuid, "--delete")
end end

View File

@ -86,6 +86,81 @@ module VagrantPlugins
execute("snapshot", machine_id, "take", snapshot_name) execute("snapshot", machine_id, "take", snapshot_name)
end end
def delete_snapshot(machine_id, snapshot_name)
# Start with 0%
last = 0
total = ""
yield 0 if block_given?
# Snapshot and report the % progress
execute("snapshot", machine_id, "delete", snapshot_name) do |type, data|
if type == :stderr
# Append the data so we can see the full view
total << data.gsub("\r", "")
# Break up the lines. We can't get the progress until we see an "OK"
lines = total.split("\n")
# The progress of the import will be in the last line. Do a greedy
# regular expression to find what we're looking for.
match = /.+(\d{2})%/.match(lines.last)
if match
current = match[1].to_i
if current > last
last = current
yield current if block_given?
end
end
end
end
end
def list_snapshots(machine_id)
output = execute(
"snapshot", machine_id, "list", "--machinereadable",
retryable: true)
result = []
output.split("\n").each do |line|
if line =~ /^SnapshotName.*?="(.+?)"$/i
result << $1.to_s
end
end
result.sort
rescue Vagrant::Errors::VBoxManageError => e
return [] if e.extra_data[:stdout].include?("does not have")
raise
end
def restore_snapshot(machine_id, snapshot_name)
# Start with 0%
last = 0
total = ""
yield 0 if block_given?
execute("snapshot", machine_id, "restore", snapshot_name) do |type, data|
if type == :stderr
# Append the data so we can see the full view
total << data.gsub("\r", "")
# Break up the lines. We can't get the progress until we see an "OK"
lines = total.split("\n")
# The progress of the import will be in the last line. Do a greedy
# regular expression to find what we're looking for.
match = /.+(\d{2})%/.match(lines.last)
if match
current = match[1].to_i
if current > last
last = current
yield current if block_given?
end
end
end
end
end
def delete def delete
execute("unregistervm", @uuid, "--delete") execute("unregistervm", @uuid, "--delete")
end end

View File

@ -33,6 +33,11 @@ module VagrantPlugins
require_relative "cap" require_relative "cap"
Cap Cap
end end
provider_capability(:virtualbox, :snapshot_list) do
require_relative "cap"
Cap
end
end end
autoload :Action, File.expand_path("../action", __FILE__) autoload :Action, File.expand_path("../action", __FILE__)

View File

@ -1501,6 +1501,17 @@ en:
Post install message from the '%{name}' plugin: Post install message from the '%{name}' plugin:
%{message} %{message}
snapshot:
not_supported: |-
This provider doesn't support snapshots.
This may be intentional or this may be a bug. If this provider
should support snapshots, then please report this as a bug to the
maintainer of the provider.
no_push_snapshot: |-
No pushed snapshot found!
Use `vagrant snapshot push` to push a snapshot to restore to.
status: status:
aborted: |- aborted: |-
The VM is in an aborted state. This means that it was abruptly The VM is in an aborted state. This means that it was abruptly
@ -1765,6 +1776,26 @@ en:
set_name: set_name:
setting_name: |- setting_name: |-
Setting the name of the VM: %{name} Setting the name of the VM: %{name}
snapshot:
deleting: |-
Deleting the snapshot '%{name}'...
deleted: |-
Snapshot deleted!
list_none: |-
No snapshots have been taken yet!
list_none_detail: |-
You can take a snapshot using `vagrant snapshot save`. Note that
not all providers support this yet. Once a snapshot is taken, you
can list them using this command, and use commands such as
`vagrant snapshot restore` to go back to a certain snapshot.
restoring: |-
Restoring the snapshot '%{name}'...
saving: |-
Snapshotting the machine as '%{name}'...
saved: |-
Snapshot saved! You can restore the snapshot at any time by
using `vagrant snapshot restore`. You can delete it using
`vagrant snapshot delete`.
suspend: suspend:
suspending: Saving VM state and suspending execution... suspending: Saving VM state and suspending execution...

View File

@ -0,0 +1,52 @@
require File.expand_path("../../../../../base", __FILE__)
require Vagrant.source_root.join("plugins/commands/snapshot/command/pop")
describe VagrantPlugins::CommandSnapshot::Command::Pop 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(:guest) { double("guest") }
let(:host) { double("host") }
let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }
let(:argv) { [] }
subject { described_class.new(argv, iso_env) }
before do
allow(subject).to receive(:with_target_vms) { |&block| block.call machine }
end
describe "execute" do
it "calls snapshot_restore with the last pushed snapshot" do
machine.id = "foo"
allow(machine.provider).to receive(:capability).
with(:snapshot_list).and_return(["push_2_0", "push_1_0"])
expect(machine).to receive(:action) do |name, opts|
expect(name).to eq(:snapshot_restore)
expect(opts[:snapshot_name]).to eq("push_2_0")
end
expect(subject.execute).to eq(0)
end
it "isn't an error if no matching snapshot" do
machine.id = "foo"
allow(machine.provider).to receive(:capability).
with(:snapshot_list).and_return(["foo"])
expect(machine).to_not receive(:action)
expect(subject.execute).to eq(0)
end
end
end

View File

@ -0,0 +1,46 @@
require File.expand_path("../../../../../base", __FILE__)
require Vagrant.source_root.join("plugins/commands/snapshot/command/push")
describe VagrantPlugins::CommandSnapshot::Command::Push 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(:guest) { double("guest") }
let(:host) { double("host") }
let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }
let(:argv) { [] }
subject { described_class.new(argv, iso_env) }
before do
allow(subject).to receive(:with_target_vms) { |&block| block.call machine }
end
describe "execute" do
it "calls snapshot_save with a random snapshot name" do
machine.id = "foo"
expect(machine).to receive(:action) do |name, opts|
expect(name).to eq(:snapshot_save)
expect(opts[:snapshot_name]).to match(/^push_/)
end
expect(subject.execute).to eq(0)
end
it "doesn't snapshot a non-existent machine" do
machine.id = nil
expect(machine).to_not receive(:action)
expect(subject.execute).to eq(0)
end
end
end

View File

@ -0,0 +1,31 @@
require "pathname"
require "tmpdir"
require File.expand_path("../../../../base", __FILE__)
describe Vagrant::Action::Builtin::IsEnvSet do
let(:app) { lambda { |env| } }
let(:env) { { } }
describe "#call" do
it "sets result to true if it is set" do
env[:bar] = true
subject = described_class.new(app, env, :bar)
expect(app).to receive(:call).with(env)
subject.call(env)
expect(env[:result]).to be_true
end
it "sets result to false if it isn't set" do
subject = described_class.new(app, env, :bar)
expect(app).to receive(:call).with(env)
subject.call(env)
expect(env[:result]).to be_false
end
end
end

View File

@ -108,6 +108,7 @@
<li<%= sidebar_current("cli-reload") %>><a href="/v2/cli/reload.html">reload</a></li> <li<%= sidebar_current("cli-reload") %>><a href="/v2/cli/reload.html">reload</a></li>
<li<%= sidebar_current("cli-resume") %>><a href="/v2/cli/resume.html">resume</a></li> <li<%= sidebar_current("cli-resume") %>><a href="/v2/cli/resume.html">resume</a></li>
<li<%= sidebar_current("cli-share") %>><a href="/v2/cli/share.html">share</a></li> <li<%= sidebar_current("cli-share") %>><a href="/v2/cli/share.html">share</a></li>
<li<%= sidebar_current("cli-snapshot") %>><a href="/v2/cli/snapshot.html">snapshot</a></li>
<li<%= sidebar_current("cli-ssh") %>><a href="/v2/cli/ssh.html">ssh</a></li> <li<%= sidebar_current("cli-ssh") %>><a href="/v2/cli/ssh.html">ssh</a></li>
<li<%= sidebar_current("cli-ssh_config") %>><a href="/v2/cli/ssh_config.html">ssh-config</a></li> <li<%= sidebar_current("cli-ssh_config") %>><a href="/v2/cli/ssh_config.html">ssh-config</a></li>
<li<%= sidebar_current("cli-status") %>><a href="/v2/cli/status.html">status</a></li> <li<%= sidebar_current("cli-status") %>><a href="/v2/cli/status.html">status</a></li>

View File

@ -0,0 +1,79 @@
---
page_title: "vagrant snapshot - Command-Line Interface"
sidebar_current: "cli-snapshot"
---
# Snapshot
**Command: `vagrant snapshot`**
This is the command used to manage snapshots with the guest machine.
Snapshots record a point-in-time state of a guest machine. You can then
quickly restore to this environment. This lets you experiment and try things
and quickly restore back to a previous state.
Snapshotting is not supported by every provider. If it isn't supported,
Vagrant will give you an error message.
The main functionality of this command is exposed via even more subcommands:
* `push`
* `pop`
* `save`
* `restore`
* `list`
* `delete`
# Snapshot Push
**Command: `vagrant snapshot push`**
This takes a snapshot and pushes it onto the snapshot stack.
This is a shorthand for `vagrant snapshot save` where you don't need
to specify a name. When you call the inverse `vagrant snapshot pop`, it will
restore the pushed state.
~> **Warning:** If you are using `push` and `pop`, avoid using `save`
and `restore` which are unsafe to mix.
# Snapshot Pop
**Command: `vagrant snapshot pop`**
This command is the inverse of `vagrant snapshot push`: it will restore
the pushed state.
# Snapshot Save
**Command: `vagrant snapshot save NAME`**
This command saves a new named snapshot. If this command is used, the
`push` and `pop` subcommands cannot be safely used.
# Snapshot Restore
**Command: `vagrant snapshot restore NAME`**
This command restores the named snapshot.
# Snapshot List
**Command: `vagrant snapshot list`**
This command will list all the snapshots taken.
# Snapshot Delete
**Command: `vagrant snapshot delete NAME`**
This command will delete the named snapshot.
Some providers require all "child" snapshots to be deleted first. Vagrant
itself doesn't track what these children are. If this is the case (such
as with VirtualBox), then you must be sure to delete the snapshots in the
reverse order they were taken.
This command is typically _much faster_ if the machine is halted prior to
snapshotting. If this isn't an option, or isn't ideal, then the deletion
can also be done online with most providers.