Class: Vagrant::Box

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/vagrant/box.rb,
lib/vagrant/box/remote.rb

Overview

Represents a "box," which is a package Vagrant environment that is used as a base image when creating a new guest machine.

Defined Under Namespace

Modules: Remote

Constant Summary collapse

REQUIRED_METADATA_FIELDS =

The required fields in a boxes metadata.json file

["provider"]
BOX_UPDATE_CHECK_INTERVAL =

Number of seconds to wait between checks for box updates

3600

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, provider, version, directory, architecture: nil, metadata_url: nil, hook: nil) ⇒ Box

This is used to initialize a box.

Parameters:

  • name (String)

    Logical name of the box.

  • provider (Symbol)

    The provider that this box implements.

  • directory (Pathname)

    The directory where this box exists on disk.

  • architecture (String) (defaults to: nil)

    Architecture the box was built for

  • metadata_url (String) (defaults to: nil)

    Metadata URL for box

  • hook (Hook) (defaults to: nil)

    A hook to apply to the box downloader, for example, for authentication

Raises:



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/vagrant/box.rb', line 76

def initialize(name, provider, version, directory, architecture: nil, metadata_url: nil, hook: nil)
  @name      = name
  @version   = version
  @provider  = provider
  @directory = directory
  @architecture = architecture
  @metadata_url = 
  @hook = hook

   = directory.join("metadata.json")
  raise Errors::BoxMetadataFileNotFound, name: @name if !.file?

  begin
    @metadata = JSON.parse(directory.join("metadata.json").read)
    (@metadata)
  rescue JSON::ParserError
    raise Errors::BoxMetadataCorrupted, name: @name
  end

  @logger = Log4r::Logger.new("vagrant::box")
end

Instance Attribute Details

#architectureString (readonly)

This is the architecture that this box is build for.

Returns:

  • (String)


43
44
45
# File 'lib/vagrant/box.rb', line 43

def architecture
  @architecture
end

#directoryPathname (readonly)

This is the directory on disk where this box exists.

Returns:

  • (Pathname)


53
54
55
# File 'lib/vagrant/box.rb', line 53

def directory
  @directory
end

#metadataHash (readonly)

This is the metadata for the box. This is read from the "metadata.json" file that all boxes require.

Returns:

  • (Hash)


59
60
61
# File 'lib/vagrant/box.rb', line 59

def 
  @metadata
end

#metadata_urlString (readonly)

This is the URL to the version info and other metadata for this box.

Returns:

  • (String)


65
66
67
# File 'lib/vagrant/box.rb', line 65

def 
  @metadata_url
end

#nameString (readonly)

The box name. This is the logical name used when adding the box.

Returns:

  • (String)


33
34
35
# File 'lib/vagrant/box.rb', line 33

def name
  @name
end

#providerSymbol (readonly)

This is the provider that this box is built for.

Returns:

  • (Symbol)


38
39
40
# File 'lib/vagrant/box.rb', line 38

def provider
  @provider
end

#versionString (readonly)

The version of this box.

Returns:

  • (String)


48
49
50
# File 'lib/vagrant/box.rb', line 48

def version
  @version
end

Instance Method Details

#<=>(other) ⇒ Object

Implemented for comparison with other boxes. Comparison is implemented by comparing names, providers, and architectures.



253
254
255
256
257
258
259
# File 'lib/vagrant/box.rb', line 253

def <=>(other)
  return super if !other.is_a?(self.class)

  # Comparison is done by composing the name and provider
  "#{@name}-#{@version}-#{@provider}-#{@architecture}" <=>
  "#{other.name}-#{other.version}-#{other.provider}-#{other.architecture}"
end

#automatic_update_check_allowed?Boolean

Check if a box update check is allowed. Uses a file in the box data directory to track when the last auto update check was performed and returns true if the BOX_UPDATE_CHECK_INTERVAL has passed.

Returns:

  • (Boolean)


217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/vagrant/box.rb', line 217

def automatic_update_check_allowed?
  check_path = directory.join("box_update_check")
  if check_path.exist?
    last_check_span = Time.now.to_i - check_path.mtime.to_i
    if last_check_span < BOX_UPDATE_CHECK_INTERVAL
      @logger.info("box update check is under the interval threshold")
      return false
    end
  end
  FileUtils.touch(check_path)
  true
end

#destroy!Object

This deletes the box. This is NOT undoable.



111
112
113
114
115
116
117
118
119
120
# File 'lib/vagrant/box.rb', line 111

def destroy!
  # Delete the directory to delete the box.
  FileUtils.rm_r(@directory)

  # Just return true always
  true
rescue Errno::ENOENT
  # This means the directory didn't exist. Not a problem.
  return true
end

#has_update?(version = nil, download_options: {}) ⇒ Array

Checks if the box has an update and returns the metadata, version, and provider. If the box doesn't have an update that satisfies the constraints, it will return nil.

This will potentially make a network call if it has to load the metadata from the network.

Parameters:

  • version (String) (defaults to: nil)

    Version constraints the update must satisfy. If nil, the version constrain defaults to being a larger version than this box.

Returns:

  • (Array)


191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/vagrant/box.rb', line 191

def has_update?(version=nil, download_options: {})
  if !@metadata_url
    raise Errors::BoxUpdateNoMetadata, name: @name
  end

  if download_options.delete(:automatic_check) && !automatic_update_check_allowed?
    @logger.info("Skipping box update check")
    return
  end

  version += ", " if version
  version ||= ""
  version += "> #{@version}"
  md      = self.(download_options)
  newer   = md.version(version, provider: @provider, architecture: @architecture)
  return nil if !newer

  [md, newer, newer.provider(@provider, @architecture)]
end

#in_use?(index) ⇒ Array<MachineIndex::Entry>

Checks if this box is in use according to the given machine index and returns the entries that appear to be using the box.

The entries returned, if any, are not tested for validity with MachineIndex::Entry#valid?, so the caller should do that if the caller cares.

Parameters:

Returns:



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/vagrant/box.rb', line 131

def in_use?(index)
  results = []
  index.each do |entry|
    box_data = entry.extra_data["box"]
    next if !box_data

    # If all the data matches, record it
    if box_data["name"] == self.name &&
      box_data["provider"] == self.provider.to_s &&
      box_data["architecture"] == self.architecture &&
      box_data["version"] == self.version.to_s
      results << entry
    end
  end

  return nil if results.empty?
  results
end

#load_metadata(download_options = {}) ⇒ BoxMetadata

Loads the metadata URL and returns the latest metadata associated with this box.

Parameters:

  • download_options (Hash) (defaults to: {})

    Options to pass to the downloader.

Returns:



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/vagrant/box.rb', line 155

def (download_options={})
  tf = Tempfile.new("vagrant-load-metadata")
  tf.close

  url = @metadata_url
  if File.file?(url) || url !~ /^[a-z0-9]+:.*$/i
    url = File.expand_path(url)
    url = Util::Platform.cygwin_windows_path(url)
    url = "file:#{url}"
  end

  opts = { headers: ["Accept: application/json"] }.merge(download_options)
  d = Util::Downloader.new(url, tf.path, opts)
  if @hook
    @hook.call(:authenticate_box_downloader, downloader: d)
  end
  d.download!
  BoxMetadata.new(File.open(tf.path, "r"), url: url)
rescue Errors::DownloaderError => e
  raise Errors::BoxMetadataDownloadError,
    message: e.extra_data[:message]
ensure
  tf.unlink if tf
end

#repackage(path) ⇒ Boolean

This repackages this box and outputs it to the given path.

Parameters:

  • path (Pathname)

    The full path (filename included) of where to output this box.

Returns:

  • (Boolean)

    true if this succeeds.



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/vagrant/box.rb', line 235

def repackage(path)
  @logger.debug("Repackaging box '#{@name}' to: #{path}")

  Util::SafeChdir.safe_chdir(@directory) do
    # Find all the files in our current directory and tar it up!
    files = Dir.glob(File.join(".", "**", "*")).select { |f| File.file?(f) }

    # Package!
    Util::Subprocess.execute("bsdtar", "-czf", path.to_s, *files)
  end

  @logger.info("Repackaged box '#{@name}' successfully: #{path}")

  true
end

#validate_metadata_json(metadata) ⇒ Object



98
99
100
101
102
103
104
105
106
107
108
# File 'lib/vagrant/box.rb', line 98

def ()
  metatdata_fields = .keys
  REQUIRED_METADATA_FIELDS.each do |field|
    if !metatdata_fields.include?(field)
      raise Errors::BoxMetadataMissingRequiredFields,
        name: @name,
        required_field: field,
        all_fields: REQUIRED_METADATA_FIELDS.join(", ")
    end
  end
end