commit
51f5b9e036
|
@ -20,6 +20,7 @@ module Vagrant
|
|||
autoload :HandleBox, "vagrant/action/builtin/handle_box"
|
||||
autoload :HandleBoxUrl, "vagrant/action/builtin/handle_box_url"
|
||||
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 :Lock, "vagrant/action/builtin/lock"
|
||||
autoload :Message, "vagrant/action/builtin/message"
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -42,6 +42,9 @@ module VagrantPlugins
|
|||
autoload :SaneDefaults, File.expand_path("../action/sane_defaults", __FILE__)
|
||||
autoload :SetName, File.expand_path("../action/set_name", __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__)
|
||||
|
||||
# Include the built-in modules so that we can use them as top-level
|
||||
|
@ -222,6 +225,59 @@ module VagrantPlugins
|
|||
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.
|
||||
def self.action_ssh
|
||||
Vagrant::Action::Builder.new.tap do |b|
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -22,6 +22,13 @@ module VagrantPlugins
|
|||
def self.nic_mac_addresses(machine)
|
||||
machine.provider.driver.read_mac_addresses
|
||||
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
|
||||
|
|
|
@ -386,7 +386,8 @@ module VagrantPlugins
|
|||
if errored
|
||||
raise Vagrant::Errors::VBoxManageError,
|
||||
command: command.inspect,
|
||||
stderr: r.stderr
|
||||
stderr: r.stderr,
|
||||
stdout: r.stdout
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -89,6 +89,7 @@ module VagrantPlugins
|
|||
:create_host_only_network,
|
||||
:create_snapshot,
|
||||
:delete,
|
||||
:delete_snapshot,
|
||||
:delete_unused_host_only_networks,
|
||||
:discard_saved_state,
|
||||
:enable_adapters,
|
||||
|
@ -97,6 +98,7 @@ module VagrantPlugins
|
|||
:forward_ports,
|
||||
:halt,
|
||||
:import,
|
||||
:list_snapshots,
|
||||
:read_forwarded_ports,
|
||||
:read_bridged_interfaces,
|
||||
:read_dhcp_servers,
|
||||
|
@ -113,6 +115,7 @@ module VagrantPlugins
|
|||
:read_vms,
|
||||
:reconfig_host_only,
|
||||
:remove_dhcp_server,
|
||||
:restore_snapshot,
|
||||
:resume,
|
||||
:set_mac_address,
|
||||
:set_name,
|
||||
|
|
|
@ -608,6 +608,85 @@ module VagrantPlugins
|
|||
execute("showvminfo", uuid)
|
||||
return true
|
||||
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
|
||||
|
|
|
@ -90,6 +90,81 @@ module VagrantPlugins
|
|||
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
|
||||
|
||||
def delete
|
||||
execute("unregistervm", @uuid, "--delete")
|
||||
end
|
||||
|
|
|
@ -86,6 +86,81 @@ module VagrantPlugins
|
|||
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
|
||||
|
||||
def delete
|
||||
execute("unregistervm", @uuid, "--delete")
|
||||
end
|
||||
|
|
|
@ -33,6 +33,11 @@ module VagrantPlugins
|
|||
require_relative "cap"
|
||||
Cap
|
||||
end
|
||||
|
||||
provider_capability(:virtualbox, :snapshot_list) do
|
||||
require_relative "cap"
|
||||
Cap
|
||||
end
|
||||
end
|
||||
|
||||
autoload :Action, File.expand_path("../action", __FILE__)
|
||||
|
|
|
@ -1501,6 +1501,17 @@ en:
|
|||
Post install message from the '%{name}' plugin:
|
||||
|
||||
%{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:
|
||||
aborted: |-
|
||||
The VM is in an aborted state. This means that it was abruptly
|
||||
|
@ -1765,6 +1776,26 @@ en:
|
|||
set_name:
|
||||
setting_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:
|
||||
suspending: Saving VM state and suspending execution...
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -108,6 +108,7 @@
|
|||
<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-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_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>
|
||||
|
|
|
@ -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.
|
Loading…
Reference in New Issue