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, **opts) ⇒ 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.

Raises:



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

def initialize(name, provider, version, directory, **opts)
  @name      = name
  @version   = version
  @provider  = provider
  @directory = directory
  @metadata_url = opts[:metadata_url]

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



217
218
219
220
221
222
223
# File 'lib/vagrant/box.rb', line 217

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)


181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/vagrant/box.rb', line 181

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.



80
81
82
83
84
85
86
87
88
89
# File 'lib/vagrant/box.rb', line 80

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)


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

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:



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

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)

    Options to pass to the downloader.

Returns:



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/vagrant/box.rb', line 123

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)
  Util::Downloader.new(url, tf.path, **opts).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.



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/vagrant/box.rb', line 199

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