Upgrade V1-style dotfile to V2

See the code and comments for details on how this is done. As usual, we
are very careful about this so as not to inadvertently destruct real
user data.
This commit is contained in:
Mitchell Hashimoto 2012-12-26 22:24:45 -08:00
parent 495479f480
commit 4e649cc987
4 changed files with 141 additions and 2 deletions

View File

@ -1,4 +1,5 @@
require 'fileutils'
require 'json'
require 'pathname'
require 'set'
@ -517,7 +518,61 @@ module Vagrant
#
# @param [Pathname] path The path to the dotfile
def upgrade_v1_dotfile(path)
raise "V1 environment detected. An auto-upgrade process will be made soon."
@logger.info("Upgrading V1 dotfile to V2 directory structure...")
# First, verify the file isn't empty. If it is an empty file, we
# just delete it and go on with life.
contents = path.read.strip
if contents.strip == ""
@logger.info("V1 dotfile was empty. Removing and moving on.")
path.delete
return
end
# Otherwise, verify there is valid JSON in here since a Vagrant
# environment would always ensure valid JSON. This is a sanity check
# to make sure we don't nuke a dotfile that is not ours...
@logger.debug("Attempting to parse JSON of V1 file")
json_data = nil
begin
json_data = JSON.parse(contents)
@logger.debug("JSON parsed successfully. Things are okay.")
rescue JSON::ParserError
# The file could've been tampered with since Vagrant 1.0.x is
# supposed to ensure that the contents are valid JSON. Show an error.
raise Errors::DotfileUpgradeJSONError,
:state_file => path.to_s
end
# Alright, let's upgrade this guy to the new structure. Start by
# backing up the old dotfile.
backup_file = path.dirname.join(".vagrant.v1.#{Time.now.to_i}")
@logger.info("Renaming old dotfile to: #{backup_file}")
path.rename(backup_file)
# Now, we create the actual local data directory. This should succeed
# this time since we renamed the old conflicting V1.
setup_local_data_path
if json_data["active"]
@logger.debug("Upgrading to V2 style for each active VM")
json_data["active"].each do |name, id|
@logger.info("Upgrading dotfile: #{name} (#{id})")
# Create the machine configuration directory
directory = @local_data_path.join("machines/#{name}/virtualbox")
FileUtils.mkdir_p(directory)
# Write the ID file
directory.join("id").open("w+") do |f|
f.write(id)
end
end
end
# Upgrade complete! Let the user know
@ui.info(I18n.t("vagrant.general.upgraded_v1_dotfile",
:backup_path => backup_file.to_s))
end
end
end

View File

@ -166,6 +166,10 @@ module Vagrant
error_key(:dotfile_is_directory)
end
class DotfileUpgradeJSONError < VagrantError
error_key(:dotfile_upgrade_json_error)
end
class DownloaderFileDoesntExist < VagrantError
status_code(37)
error_key(:file_missing, "vagrant.downloaders.file")

View File

@ -9,6 +9,17 @@ en:
Old: %{old}
New: %{new}
upgraded_v1_dotfile: |-
A Vagrant 1.0.x state file was found for this environment. Vagrant has
gone ahead and auto-upgraded this to the latest format. Everything
should continue working as normal. Beware, however, that older versions
of Vagrant may no longer be used with this environment.
However, in case anything went wrong, the old dotfile was backed up
to the location below. If everything is okay, it is safe to remove
this backup.
Backup: %{backup_path}
#-------------------------------------------------------------------------------
# Translations for exception classes
@ -55,6 +66,24 @@ en:
this command in another directory. If you aren't in a home directory,
then please rename ".vagrant" to something else, or configure Vagrant
to use another filename by modifying `config.vagrant.dotfile_name`.
dotfile_upgrade_json_error: |-
A Vagrant 1.0.x local state file was found. Vagrant is able to upgrade
this to the latest format automatically, however various checks are
put in place to verify data isn't incorrectly deleted. In this case,
the old state file was not valid JSON. Vagrant 1.0.x would store state
as valid JSON, meaning that this file was probably tampered with or
manually edited. Vagrant's auto-upgrade process cannot continue in this
case.
In most cases, this can be resolve by simply removing the state file.
Note however though that if Vagrant was previously managing virtual
machines, they may be left in an "orphan" state. That is, if they are
running or exist, they'll have to manually be removed.
If you're unsure what to do, ask the Vagrant mailing list or contact
support.
State file path: %{state_file}
environment_locked: |-
An instance of Vagrant is already running. Only one instance of Vagrant
may run at any given time to avoid problems with VirtualBox inconsistencies

View File

@ -1,5 +1,7 @@
require File.expand_path("../../base", __FILE__)
require "json"
require "pathname"
require "tempfile"
require "vagrant/util/file_mode"
@ -91,6 +93,55 @@ describe Vagrant::Environment do
instance = described_class.new(:local_data_path => dir)
instance.local_data_path.to_s.should == dir
end
describe "upgrading V1 dotfiles" do
let(:v1_dotfile_tempfile) { Tempfile.new("vagrant") }
let(:v1_dotfile) { Pathname.new(v1_dotfile_tempfile.path) }
let(:local_data_path) { v1_dotfile_tempfile.path }
let(:instance) { described_class.new(:local_data_path => local_data_path) }
it "should be fine if dotfile is empty" do
v1_dotfile.open("w+") do |f|
f.write("")
end
expect { instance }.to_not raise_error
Pathname.new(local_data_path).should be_directory
end
it "should upgrade all active VMs" do
active_vms = {
"foo" => "foo_id",
"bar" => "bar_id"
}
v1_dotfile.open("w+") do |f|
f.write(JSON.dump({
"active" => active_vms
}))
end
expect { instance }.to_not raise_error
local_data_pathname = Pathname.new(local_data_path)
foo_id_file = local_data_pathname.join("machines/foo/virtualbox/id")
foo_id_file.should be_file
foo_id_file.read.should == "foo_id"
bar_id_file = local_data_pathname.join("machines/bar/virtualbox/id")
bar_id_file.should be_file
bar_id_file.read.should == "bar_id"
end
it "should raise an error if invalid JSON" do
v1_dotfile.open("w+") do |f|
f.write("bad")
end
expect { instance }.
to raise_error(Vagrant::Errors::DotfileUpgradeJSONError)
end
end
end
describe "default provider" do
@ -239,7 +290,7 @@ VF
it "should return a machine object with the machine configuration" do
# Create a provider
foo_config = Class.new do
foo_config = Class.new(Vagrant.plugin("2", :config)) do
attr_accessor :value
end