Class: Vagrant::Vagrantfile

Inherits:
Object
  • Object
show all
Defined in:
lib/vagrant/vagrantfile.rb

Overview

This class provides a way to load and access the contents of a Vagrantfile.

This class doesn't actually load Vagrantfiles, parse them, merge them, etc. That is the job of Config::Loader. This class, on the other hand, has higher-level operations on a loaded Vagrantfile such as looking up the defined machines, loading the configuration of a specific machine/provider combo, etc.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(loader, keys) ⇒ Vagrantfile

Initializes by loading a Vagrantfile.

Parameters:

  • loader (Config::Loader)

    Configuration loader that should already be configured with the proper Vagrantfile locations. This usually comes from Environment

  • keys (Array<Symbol>)

    The Vagrantfiles to load and the order to load them in (keys within the loader).


26
27
28
29
30
31
# File 'lib/vagrant/vagrantfile.rb', line 26

def initialize(loader, keys)
  @keys   = keys
  @loader = loader
  @config, _ = loader.load(keys)
  @logger = Log4r::Logger.new("vagrant::vagrantfile")
end

Instance Attribute Details

#configObject (readonly)

This is the configuration loaded as-is given the loader and keys to #initialize.


17
18
19
# File 'lib/vagrant/vagrantfile.rb', line 17

def config
  @config
end

Instance Method Details

#machine(name, provider, boxes, data_path, env) ⇒ Machine

Returns a Machine for the given name and provider that is represented by this Vagrantfile.

Parameters:

  • name (Symbol)

    Name of the machine.

  • provider (Symbol)

    The provider the machine should be backed by (required for provider overrides).

  • boxes (BoxCollection)

    BoxCollection to look up the box Vagrantfile.

  • data_path (Pathname)

    Path where local machine data can be stored.

  • env (Environment)

    The environment running this machine

Returns:


45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/vagrant/vagrantfile.rb', line 45

def machine(name, provider, boxes, data_path, env)
  # Load the configuration for the machine
  results = machine_config(name, provider, boxes, data_path)
  box             = results[:box]
  config          = results[:config]
  config_errors   = results[:config_errors]
  config_warnings = results[:config_warnings]
  provider_cls    = results[:provider_cls]
  provider_options = results[:provider_options]

  # If there were warnings or errors we want to output them
  if !config_warnings.empty? || !config_errors.empty?
    # The color of the output depends on whether we have warnings
    # or errors...
    level  = config_errors.empty? ? :warn : :error
    output = Util::TemplateRenderer.render(
      "config/messages",
      warnings: config_warnings,
      errors: config_errors).chomp
    env.ui.send(level, I18n.t("vagrant.general.config_upgrade_messages",
                           name: name,
                           output: output))

    # If we had errors, then we bail
    raise Errors::ConfigUpgradeErrors if !config_errors.empty?
  end

  # Get the provider configuration from the final loaded configuration
  provider_config = config.vm.get_provider_config(provider)

  # Create machine data directory if it doesn't exist
  # XXX: Permissions error here.
  FileUtils.mkdir_p(data_path)

  # Create the machine and cache it for future calls. This will also
  # return the machine from this method.
  return Machine.new(name, provider, provider_cls, provider_config,
    provider_options, config, data_path, box, env, self)
end

#machine_config(name, provider, boxes, data_path = nil, validate_provider = true) ⇒ Hash<Symbol, Object>

Returns the configuration for a single machine.

When loading a box Vagrantfile, it will be prepended to the key order specified when initializing this class. Sub-machine and provider-specific overrides are appended at the end. The actual order is:

  • box
  • keys specified for #initialize
  • sub-machine
  • provider

The return value is a hash with the following keys (symbols) and values:

  • box: the Box backing the machine
  • config: the actual configuration
  • config_errors: list of errors, if any
  • config_warnings: list of warnings, if any
  • provider_cls: class of the provider backing the machine
  • provider_options: options for the provider

Parameters:

  • name (Symbol)

    Name of the machine.

  • provider (Symbol)

    The provider the machine should be backed by (required for provider overrides).

  • boxes (BoxCollection)

    BoxCollection to look up the box Vagrantfile.

  • data_path (Pathname) (defaults to: nil)

    Machine data path

Returns:

  • (Hash<Symbol, Object>)

    Various configuration parameters for a machine. See the main documentation body for more info.


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
165
166
167
168
169
170
171
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
208
209
210
211
212
213
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
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/vagrant/vagrantfile.rb', line 115

def machine_config(name, provider, boxes, data_path=nil, validate_provider=true)
  keys = @keys.dup

  sub_machine = @config.vm.defined_vms[name]
  if !sub_machine
    raise Errors::MachineNotFound,
      name: name, provider: provider
  end

  provider_plugin  = nil
  provider_cls     = nil
  provider_options = {}
  box_formats      = nil
  if provider != nil
    provider_plugin  = Vagrant.plugin("2").manager.providers[provider]
    if !provider_plugin && validate_provider
      providers  = Vagrant.plugin("2").manager.providers.to_hash.keys
      if providers
        providers_str = providers.join(', ')
      else
        providers_str = "N/A"
      end

      if providers.include? provider.downcase
        raise Errors::ProviderNotFoundSuggestion,
          machine: name, provider: provider,
          suggestion: provider.downcase, providers: providers_str
      end

      raise Errors::ProviderNotFound,
        machine: name, provider: provider, providers: providers_str
    end

    if validate_provider
      provider_cls     = provider_plugin[0]
      provider_options = provider_plugin[1]
      box_formats      = provider_options[:box_format] || provider

      # Test if the provider is usable or not
      begin
        provider_cls.usable?(true)
      rescue Errors::VagrantError => e
        raise Errors::ProviderNotUsable,
          machine: name.to_s,
          provider: provider.to_s,
          message: e.to_s
      end
    else
      box_formats = provider
    end
  end

  # Add the sub-machine configuration to the loader and keys
  vm_config_key = "#{object_id}_machine_#{name}"
  @loader.set(vm_config_key, sub_machine.config_procs)
  keys << vm_config_key

  # Load once so that we can get the proper box value
  config, config_warnings, config_errors = @loader.load(keys)

  # Track the original box so we know if we changed
  box = nil
  initial_box = original_box = config.vm.box
  initial_version = original_version = config.vm.box_version

  # Check if this machine has a local box metadata file
  # describing the existing guest. If so, load it and
  # set the box name and version to allow the actual
  # box in use to be discovered.
  if data_path
    meta_file = data_path.join("box_meta")
    if meta_file.file?
      box_meta = JSON.parse(meta_file.read)
      config.vm.box = box_meta["name"]
      config.vm.box_version = box_meta["version"]
    end
  end

  # The proc below loads the box and provider overrides. This is
  # in a proc because it may have to recurse if the provider override
  # changes the box.
  load_box_proc = lambda do
    local_keys = keys.dup

    # Load the box Vagrantfile, if there is one
    if !config.vm.box.to_s.empty? && boxes
      box = boxes.find(config.vm.box, box_formats, config.vm.box_version)
      if box
        box_vagrantfile = find_vagrantfile(box.directory)
        if box_vagrantfile && !config.vm.ignore_box_vagrantfile
          box_config_key =
            "#{boxes.object_id}_#{box.name}_#{box.provider}".to_sym
          @loader.set(box_config_key, box_vagrantfile)
          local_keys.unshift(box_config_key)
          config, config_warnings, config_errors = @loader.load(local_keys)
        elsif box_vagrantfile && config.vm.ignore_box_vagrantfile
          @logger.warn("Ignoring #{box.name} provided Vagrantfile inside box")
        end
      end
    end

    # Load provider overrides
    provider_overrides = config.vm.get_provider_overrides(provider)
    if !provider_overrides.empty?
      config_key =
        "#{object_id}_vm_#{name}_#{config.vm.box}_#{provider}".to_sym
      @loader.set(config_key, provider_overrides)
      local_keys << config_key
      config, config_warnings, config_errors = @loader.load(local_keys)
    end

    # If the box changed, then we need to reload
    if original_box != config.vm.box || original_version != config.vm.box_version
      # TODO: infinite loop protection?

      original_box = config.vm.box
      original_version = config.vm.box_version
      load_box_proc.call
    end
  end

  # Load the box and provider overrides
  load_box_proc.call

  # NOTE: In cases where the box_meta file contains stale information
  #       and the reference box no longer exists, fall back to initial
  #       configuration and attempt to load that
  if box.nil?
    @logger.warn("Failed to locate #{config.vm.box} with version #{config.vm.box_version}")
    @logger.warn("Performing lookup with initial values #{initial_box} with version #{initial_version}")
    config.vm.box = original_box = initial_box
    config.vm.box_version = original_box = initial_version
    load_box_proc.call
  end

  # Ensure box attributes are set to original values in
  # case they were modified by the local box metadata
  config.vm.box = original_box
  config.vm.box_version = original_version

  return {
    box: box,
    provider_cls: provider_cls,
    provider_options: provider_options.dup,
    config: config,
    config_warnings: config_warnings,
    config_errors: config_errors,
  }
end

#machine_namesArray<Symbol>

Returns a list of the machines that are defined within this Vagrantfile.

Returns:

  • (Array<Symbol>)

269
270
271
# File 'lib/vagrant/vagrantfile.rb', line 269

def machine_names
  @config.vm.defined_vm_keys.dup
end

#machine_names_and_optionsHash<Symbol, Hash>

Returns a list of the machine names as well as the options that were specified for that machine.

Returns:

  • (Hash<Symbol, Hash>)

277
278
279
280
281
282
283
# File 'lib/vagrant/vagrantfile.rb', line 277

def machine_names_and_options
  {}.tap do |r|
    @config.vm.defined_vms.each do |name, subvm|
      r[name] = subvm.options || {}
    end
  end
end

#primary_machine_nameSymbol

Returns the name of the machine that is designated as the "primary."

In the case of a single-machine environment, this is just the single machine name. In the case of a multi-machine environment, then this is the machine that is marked as primary, or nil if no primary machine was specified.

Returns:

  • (Symbol)

294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/vagrant/vagrantfile.rb', line 294

def primary_machine_name
  # If it is a single machine environment, then return the name
  return machine_names.first if machine_names.length == 1

  # If it is a multi-machine environment, then return the primary
  @config.vm.defined_vms.each do |name, subvm|
    return name if subvm.options[:primary]
  end

  # If no primary was specified, nil it is
  nil
end