Class: Vagrant::BoxCollection
- Inherits:
-
Object
- Object
- Vagrant::BoxCollection
- Defined in:
- lib/vagrant/box_collection.rb
Overview
Represents a collection a boxes found on disk. This provides methods for accessing/finding individual boxes, adding new boxes, or deleting boxes.
Constant Summary collapse
- TEMP_PREFIX =
"vagrant-box-add-temp-"
Instance Attribute Summary collapse
-
#directory ⇒ Pathname
readonly
The directory where the boxes in this collection are stored.
Instance Method Summary collapse
-
#add(path, name, provider = nil, force = false) ⇒ Object
This adds a new box to the system.
-
#all ⇒ Array
This returns an array of all the boxes on the system, given by their name and their provider.
-
#find(name, provider) ⇒ Box
Find a box in the collection with the given name and provider.
-
#initialize(directory) ⇒ BoxCollection
constructor
Initializes the collection.
-
#upgrade(name) ⇒ Boolean
Upgrades a V1 box with the given name to a V2 box.
Constructor Details
#initialize(directory) ⇒ BoxCollection
Initializes the collection.
42 43 44 45 |
# File 'lib/vagrant/box_collection.rb', line 42 def initialize(directory) @directory = directory @logger = Log4r::Logger.new("vagrant::box_collection") end |
Instance Attribute Details
#directory ⇒ Pathname (readonly)
The directory where the boxes in this collection are stored.
A box collection matches a very specific folder structure that Vagrant expects in order to easily manage and modify boxes. The folder structure is the following:
COLLECTION_ROOT/BOX_NAME/PROVIDER/.json
Where:
* COLLECTION_ROOT - This is the root of the box collection, and is
the directory given to the initializer.
* BOX_NAME - The name of the box. This is a logical name given by
the user of Vagrant.
* PROVIDER - The provider that the box was built for (VirtualBox,
VMWare, etc.).
* metadata.json - A simple JSON file that at the bare minimum
contains a "provider" key that matches the provider for the
box. This metadata JSON, however, can contain anything.
36 37 38 |
# File 'lib/vagrant/box_collection.rb', line 36 def directory @directory end |
Instance Method Details
#add(path, name, provider = nil, force = false) ⇒ Object
This adds a new box to the system.
There are some exceptional cases:
-
BoxAlreadyExists - The box you’re attempting to add already exists.
-
BoxProviderDoesntMatch - If the given box provider doesn’t match the actual box provider in the untarred box.
-
BoxUnpackageFailure - An invalid tar file.
-
BoxUpgradeRequired - You’re attempting to add a box when there is a V1 box with the same name that must first be upgraded.
Preconditions:
-
File given in ‘path` must exist.
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/vagrant/box_collection.rb', line 68 def add(path, name, provider=nil, force=false) # A helper to check if a box exists. We store this in a variable # since we call it multiple times. check_box_exists = lambda do |box_provider| box = find(name, box_provider) next if !box if !force @logger.error("Box already exists, can't add: #{name} #{box_provider}") raise Errors::BoxAlreadyExists, :name => name, :provider => box_provider end # We're forcing, so just delete the old box @logger.info("Box already exists, but forcing so removing: #{name} #{box_provider}") box.destroy! end log_provider = provider ? provider : "any provider" @logger.debug("Adding box: #{name} (#{log_provider}) from #{path}") # Verify the box doesn't exist early if we're given a provider. This # can potentially speed things up considerably since we don't need # to unpack any files. check_box_exists.call(provider) if provider # Verify that a V1 box doesn't exist. If it does, then we signal # to the user that we need an upgrade. raise Errors::BoxUpgradeRequired, :name => name if v1_box?(@directory.join(name)) # Create a temporary directory since we're not sure at this point if # the box we're unpackaging already exists (if no provider was given) temp_dir = Pathname.new(Dir.mktmpdir(TEMP_PREFIX)) # Extract the box into a temporary directory. @logger.debug("Unpacking box into temporary directory: #{temp_dir}") result = Util::Subprocess.execute( "bsdtar", "-v", "-x", "-C", temp_dir.to_s, "-f", path.to_s) raise Errors::BoxUnpackageFailure, :output => result.stderr.to_s if result.exit_code != 0 # If we get a V1 box, we want to update it in place if v1_box?(temp_dir) @logger.debug("Added box is a V1 box. Upgrading in place.") temp_dir = v1_upgrade(temp_dir) end # Get an instance of the box we just added before it is finalized # in the system so we can inspect and use its metadata. box = Box.new(name, provider, temp_dir) # Get the provider, since we'll need that to at the least add it # to the system or check that it matches what is given to us. box_provider = box.["provider"] if provider # Verify that the given provider matches what the box has. if box_provider.to_sym != provider @logger.error("Added box provider doesnt match expected: #{box_provider}") raise Errors::BoxProviderDoesntMatch, :expected => provider, :actual => box_provider end else # We weren't given a provider, so store this one. provider = box_provider.to_sym # Verify the box doesn't already exist check_box_exists.call(provider) end # Create the directory for this box, not including the provider box_dir = @directory.join(name) box_dir.mkpath @logger.debug("Box directory: #{box_dir}") # This is the final directory we'll move it to final_dir = box_dir.join(provider.to_s) if final_dir.exist? @logger.debug("Removing existing provider directory...") final_dir.rmtree end # Move to final destination final_dir.mkpath # Go through each child and copy them one-by-one. This avoids # an issue where on Windows cross-device directory copies are # failing for some reason. [GH-1424] temp_dir.children(true).each do |f| destination = final_dir.join(f.basename) @logger.debug("Moving: #{f} => #{destination}") FileUtils.mv(f, destination) end # Remove the temporary directory temp_dir.rmtree # Return the box find(name, provider) end |
#all ⇒ Array
This returns an array of all the boxes on the system, given by their name and their provider.
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/vagrant/box_collection.rb', line 172 def all results = [] @logger.debug("Finding all boxes in: #{@directory}") @directory.children(true).each do |child| # Ignore non-directories, since files are not interesting to # us in our folder structure. next if !child.directory? box_name = child.basename.to_s # If this is a V1 box, we still return that name, but specify # that the box is a V1 box. if v1_box?(child) @logger.debug("V1 box found: #{box_name}") results << [box_name, :virtualbox, :v1] next end # Otherwise, traverse the subdirectories and see what providers # we have. child.children(true).each do |provider| # Verify this is a potentially valid box. If it looks # correct enough then include it. if provider.directory? && provider.join("metadata.json").file? provider_name = provider.basename.to_s.to_sym @logger.debug("Box: #{box_name} (#{provider_name})") results << [box_name, provider_name] else @logger.debug("Invalid box, ignoring: #{provider}") end end end results end |
#find(name, provider) ⇒ Box
Find a box in the collection with the given name and provider.
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/vagrant/box_collection.rb', line 214 def find(name, provider) # First look directly for the box we're asking for. box_directory = @directory.join(name, provider.to_s, "metadata.json") @logger.info("Searching for box: #{name} (#{provider}) in #{box_directory}") if box_directory.file? @logger.info("Box found: #{name} (#{provider})") return Box.new(name, provider, box_directory.dirname) end # If we're looking for a VirtualBox box, then we check if there is # a V1 box. if provider == :virtualbox # Check if a V1 version of this box exists, and if so, raise an # exception notifying the caller that the box exists but needs # to be upgraded. We don't do the upgrade here because it can be # a fairly intensive activity and don't want to immediately degrade # user performance on a find. # # To determine if it is a V1 box we just do a simple heuristic # based approach. @logger.info("Searching for V1 box: #{name}") if v1_box?(@directory.join(name)) @logger.warn("V1 box found: #{name}") raise Errors::BoxUpgradeRequired, :name => name end end # Didn't find it, return nil @logger.info("Box not found: #{name} (#{provider})") nil end |
#upgrade(name) ⇒ Boolean
Upgrades a V1 box with the given name to a V2 box. If a box with the given name doesn’t exist, then a ‘BoxNotFound` exception will be raised. If the given box is found but is not a V1 box then `true` is returned because this just works fine.
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/vagrant/box_collection.rb', line 252 def upgrade(name) @logger.debug("Upgrade request for box: #{name}") box_dir = @directory.join(name) # If the box doesn't exist at all, raise an exception raise Errors::BoxNotFound, :name => name if !box_dir.directory? if v1_box?(box_dir) @logger.debug("V1 box #{name} found. Upgrading!") # First we actually perform the upgrade temp_dir = v1_upgrade(box_dir) # Rename the temporary directory to the provider. FileUtils.mv(temp_dir.to_s, box_dir.join("virtualbox").to_s) @logger.info("Box '#{name}' upgraded from V1 to V2.") end # We did it! Or the v1 box didn't exist so it doesn't matter. return true end |