Class: Vagrant::Box

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

Overview

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

Constant Summary collapse

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, 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.

  • 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:


62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/vagrant/box.rb', line 62

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

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

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

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

Instance Attribute Details

#directoryPathname (readonly)

This is the directory on disk where this box exists.

Returns:

  • (Pathname)

40
41
42
# File 'lib/vagrant/box.rb', line 40

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)

46
47
48
# File 'lib/vagrant/box.rb', line 46

def 
  @metadata
end

#metadata_urlString (readonly)

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

Returns:

  • (String)

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

def 
  @metadata_url
end

#nameString (readonly)

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

Returns:

  • (String)

25
26
27
# File 'lib/vagrant/box.rb', line 25

def name
  @name
end

#providerSymbol (readonly)

This is the provider that this box is built for.

Returns:

  • (Symbol)

30
31
32
# File 'lib/vagrant/box.rb', line 30

def provider
  @provider
end

#versionString (readonly)

The version of this box.

Returns:

  • (String)

35
36
37
# File 'lib/vagrant/box.rb', line 35

def version
  @version
end

Instance Method Details

#<=>(other) ⇒ Object

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


224
225
226
227
228
229
230
# File 'lib/vagrant/box.rb', line 224

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

  # Comparison is done by composing the name and provider
  "#{@name}-#{@version}-#{@provider}" <=>
  "#{other.name}-#{other.version}-#{other.provider}"
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)

188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/vagrant/box.rb', line 188

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.


83
84
85
86
87
88
89
90
91
92
# File 'lib/vagrant/box.rb', line 83

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)

162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/vagrant/box.rb', line 162

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)
  return nil if !newer

  [md, newer, newer.provider(@provider)]
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:


103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/vagrant/box.rb', line 103

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["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:


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

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"))
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.


206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/vagrant/box.rb', line 206

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