Class: Vagrant::Plugin::Manager

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

Overview

The Manager helps with installing, listing, and initializing plugins.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(user_file) ⇒ Manager

Returns a new instance of Manager.

Parameters:

  • user_file (Pathname)

35
36
37
38
39
40
41
42
43
44
45
# File 'lib/vagrant/plugin/manager.rb', line 35

def initialize(user_file)
  @logger = Log4r::Logger.new("vagrant::plugin::manager")
  @user_file   = StateFile.new(user_file)

  system_path  = self.class.system_plugins_file
  @system_file = nil
  @system_file = StateFile.new(system_path) if system_path && system_path.file?

  @local_file = nil
  @globalized = @localized = false
end

Instance Attribute Details

#local_fileObject (readonly)

Returns the value of attribute local_file.


32
33
34
# File 'lib/vagrant/plugin/manager.rb', line 32

def local_file
  @local_file
end

#system_fileObject (readonly)

Returns the value of attribute system_file.


31
32
33
# File 'lib/vagrant/plugin/manager.rb', line 31

def system_file
  @system_file
end

#user_fileObject (readonly)

Returns the value of attribute user_file.


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

def user_file
  @user_file
end

Class Method Details

.instanceObject


26
27
28
# File 'lib/vagrant/plugin/manager.rb', line 26

def self.instance
  @instance ||= self.new(user_plugins_file)
end

.system_plugins_fileObject

Returns the path to the [StateFile] for system plugins.


20
21
22
23
24
# File 'lib/vagrant/plugin/manager.rb', line 20

def self.system_plugins_file
  dir = Vagrant.installer_embedded_dir
  return nil if !dir
  Pathname.new(dir).join("plugins.json")
end

.user_plugins_filePathname

Returns the path to the [StateFile] for user plugins.

Returns:

  • (Pathname)

15
16
17
# File 'lib/vagrant/plugin/manager.rb', line 15

def self.user_plugins_file
  Vagrant.user_data_path.join("plugins.json")
end

Instance Method Details

#bundler_init(plugins, **opts) ⇒ nil

Initialize bundler with given plugins

Parameters:

  • plugins (Hash)

    List of plugins

Returns:

  • (nil)

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
# File 'lib/vagrant/plugin/manager.rb', line 83

def bundler_init(plugins, **opts)
  if !Vagrant.plugins_init?
    @logger.warn("Plugin initialization is disabled")
    return nil
  end

  @logger.info("Plugins:")
  plugins.each do |plugin_name, plugin_info|
    installed_version = plugin_info["installed_gem_version"]
    version_constraint = plugin_info["gem_version"]
    installed_version = 'undefined' if installed_version.to_s.empty?
    version_constraint = '> 0' if version_constraint.to_s.empty?
    @logger.info(
      "  - #{plugin_name} = [installed: " \
        "#{installed_version} constraint: " \
        "#{version_constraint}]"
    )
  end
  begin
    Vagrant::Bundler.instance.init!(plugins, **opts)
  rescue StandardError, ScriptError => err
    @logger.error("Plugin initialization error - #{err.class}: #{err}")
    err.backtrace.each do |backtrace_line|
      @logger.debug(backtrace_line)
    end
    raise Vagrant::Errors::PluginInitError, message: err.to_s
  end
end

#globalize!Hash

Enable global plugins

Returns:

  • (Hash)

    list of plugins


50
51
52
53
54
55
56
# File 'lib/vagrant/plugin/manager.rb', line 50

def globalize!
  @globalized = true
  @logger.debug("Enabling globalized plugins")
  plugins = installed_plugins
  bundler_init(plugins, global: user_file.path)
  plugins
end

#install_plugin(name, **opts) ⇒ Gem::Specification

Installs another plugin into our gem directory.

Parameters:

  • name (String)

    Name of the plugin (gem)

Returns:


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
# File 'lib/vagrant/plugin/manager.rb', line 116

def install_plugin(name, **opts)
  if opts[:env_local] && @local_file.nil?
    raise Errors::PluginNoLocalError
  end

  if name =~ /\.gem$/
    # If this is a gem file, then we install that gem locally.
    local_spec = Vagrant::Bundler.instance.install_local(name, opts)
    name       = local_spec.name
    opts[:version] = local_spec.version.to_s
  end

  plugins = installed_plugins
  plugins[name] = {
    "require"     => opts[:require],
    "gem_version" => opts[:version],
    "sources"     => opts[:sources],
  }

  if local_spec.nil?
    result = nil
    install_lambda = lambda do
      Vagrant::Bundler.instance.install(plugins, opts[:env_local]).each do |spec|
        next if spec.name != name
        next if result && result.version >= spec.version
        result = spec
      end
    end

    if opts[:verbose]
      Vagrant::Bundler.instance.verbose(&install_lambda)
    else
      install_lambda.call
    end
  else
    result = local_spec
  end

  if result
    # Add the plugin to the state file
    plugin_file = opts[:env_local] ? @local_file : @user_file
    plugin_file.add_plugin(
      result.name,
      version: opts[:version],
      require: opts[:require],
      sources: opts[:sources],
      env_local: !!opts[:env_local],
      installed_gem_version: result.version.to_s
    )
  else
    r = Gem::Dependency.new(name, opts[:version])
    result = Gem::Specification.find { |s|
      s.satisfies_requirement?(r) &&
        s.activated?
    }
    raise Errors::PluginInstallFailed,
      name: name if result.nil?
    @logger.warn("Plugin install returned no result as no new plugins were installed.")
  end
  # After install clean plugin gems to remove any cruft. This is useful
  # for removing outdated dependencies or other versions of an installed
  # plugin if the plugin is upgraded/downgraded
  Vagrant::Bundler.instance.clean(installed_plugins, local: !!opts[:local])
  result
rescue Gem::GemNotFoundException
  raise Errors::PluginGemNotFound, name: name
rescue Gem::Exception => err
  @logger.warn("Failed to install plugin: #{err}")
  @logger.debug("#{err.class}: #{err}\n#{err.backtrace.join("\n")}")
  # Try and determine a cause for the failure
  case err.message
  when /install development tools first/
    raise Errors::PluginNeedsDeveloperTools
  when /library not found in default locations/
    lib = err.message.match(/(\w+) library not found in default locations/)
    if lib.nil?
      raise Errors::BundlerError, message: err.message
    end
    raise Errors::PluginMissingLibrary,
      library: lib.captures.first,
      name: name
  when /find header files for ruby/
    raise Errors::PluginMissingRubyDev
  else
    raise Errors::BundlerError, message: err.message
  end
end

#installed_pluginsHash

This returns the list of plugins that should be enabled.

Returns:

  • (Hash)

266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/vagrant/plugin/manager.rb', line 266

def installed_plugins
  system = {}
  if @system_file
    @system_file.installed_plugins.each do |k, v|
      system[k] = v.merge("system" => true)
    end
  end
  plugin_list = Util::DeepMerge.deep_merge(system, @user_file.installed_plugins)

  if @local_file
    plugin_list = Util::DeepMerge.deep_merge(plugin_list,
      @local_file.installed_plugins)
  end

  # Sort plugins by name
  Hash[
    plugin_list.map{|plugin_name, plugin_info|
      [plugin_name, plugin_info]
    }.sort_by(&:first)
  ]
end

#installed_specsArray<Gem::Specification>

This returns the list of plugins that are installed as Gem::Specifications.

Returns:


292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/vagrant/plugin/manager.rb', line 292

def installed_specs
  installed_plugin_info = installed_plugins
  installed = Set.new(installed_plugin_info.keys)
  installed_versions = Hash[
    installed_plugin_info.map{|plugin_name, plugin_info|
      gem_version = plugin_info["gem_version"].to_s
      gem_version = "> 0" if gem_version.empty?
      [plugin_name, Gem::Requirement.new(gem_version)]
    }
  ]

  # Go through the plugins installed in this environment and
  # get the latest version of each.
  installed_map = {}
  Gem::Specification.find_all.each do |spec|
    # Ignore specs that aren't in our installed list
    next if !installed.include?(spec.name)

    next if installed_versions[spec.name] &&
      !installed_versions[spec.name].satisfied_by?(spec.version)

    # If we already have a newer version in our list of installed,
    # then ignore it
    next if installed_map.key?(spec.name) &&
      installed_map[spec.name].version >= spec.version

    installed_map[spec.name] = spec
  end

  installed_map.values
end

#load_plugins(plugins) ⇒ nil

Loads the requested plugins into the Vagrant runtime

Parameters:

  • plugins (Hash)

    List of plugins to load

Returns:

  • (nil)

328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/vagrant/plugin/manager.rb', line 328

def load_plugins(plugins)
  if !Vagrant.plugins_enabled?
    @logger.warn("Plugin loading is disabled")
    return
  end

  if plugins.nil?
    @logger.debug("No plugins provided for loading")
    return
  end

  begin
    @logger.info("Loading plugins...")
    plugins.each do |plugin_name, plugin_info|
      if plugin_info["require"].to_s.empty?
        begin
          @logger.info("Loading plugin `#{plugin_name}` with default require: `#{plugin_name}`")
          require plugin_name
        rescue LoadError => err
          if plugin_name.include?("-")
            plugin_slash = plugin_name.gsub("-", "/")
            @logger.error("Failed to load plugin `#{plugin_name}` with default require. - #{err.class}: #{err}")
            @logger.info("Loading plugin `#{plugin_name}` with slash require: `#{plugin_slash}`")
            require plugin_slash
          else
            raise
          end
        end
      else
        @logger.debug("Loading plugin `#{plugin_name}` with custom require: `#{plugin_info["require"]}`")
        require plugin_info["require"]
      end
      @logger.debug("Successfully loaded plugin `#{plugin_name}`.")
    end
    if defined?(::Bundler)
      @logger.debug("Bundler detected in use. Loading `:plugins` group.")
      ::Bundler.require(:plugins)
    end
  rescue ScriptError, StandardError => err
    @logger.error("Plugin loading error: #{err.class} - #{err}")
    err.backtrace.each do |backtrace_line|
      @logger.debug(backtrace_line)
    end
    raise Vagrant::Errors::PluginLoadError, message: err.to_s
  end
  nil
end

#localize!(env) ⇒ Hash?

Enable environment local plugins

Parameters:

Returns:

  • (Hash, nil)

    list of plugins


62
63
64
65
66
67
68
69
70
71
72
# File 'lib/vagrant/plugin/manager.rb', line 62

def localize!(env)
  @localized = true
  if env.local_data_path
    @logger.debug("Enabling localized plugins")
    @local_file = StateFile.new(env.local_data_path.join("plugins.json"))
    Vagrant::Bundler.instance.environment_path = env.local_data_path
    plugins = local_file.installed_plugins
    bundler_init(plugins, local: local_file.path)
    plugins
  end
end

#plugin_installed?(name, version = nil) ⇒ Boolean

Check if the requested plugin is installed

Parameters:

  • name (String)

    Name of plugin

  • version (String) (defaults to: nil)

    Specific version of the plugin

Returns:

  • (Boolean)

381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
# File 'lib/vagrant/plugin/manager.rb', line 381

def plugin_installed?(name, version=nil)
  # Make the requirement object
  version = Gem::Requirement.new([version.to_s]) if version

  # If plugins are loaded, check for match in loaded specs
  if ready?
    return installed_specs.any? do |s|
      match = s.name == name
      next match if !version
      next match && version.satisfied_by?(s.version)
    end
  end

  # Plugins are not loaded yet so check installed plugin data
  plugin_info = installed_plugins[name]
  return false if !plugin_info
  return !!plugin_info if version.nil? || plugin_info["installed_gem_version"].nil?
  installed_version = Gem::Version.new(plugin_info["installed_gem_version"])
  version.satisfied_by?(installed_version)
end

#ready?Boolean

Returns local and global plugins are loaded.

Returns:

  • (Boolean)

    local and global plugins are loaded


75
76
77
# File 'lib/vagrant/plugin/manager.rb', line 75

def ready?
  @globalized && @localized
end

#uninstall_plugin(name, **opts) ⇒ Object

Uninstalls the plugin with the given name.

Parameters:

  • name (String)

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
# File 'lib/vagrant/plugin/manager.rb', line 207

def uninstall_plugin(name, **opts)
  if @system_file
    if !@user_file.has_plugin?(name) && @system_file.has_plugin?(name)
      raise Errors::PluginUninstallSystem,
        name: name
    end
  end

  if opts[:env_local] && @local_file.nil?
    raise Errors::PluginNoLocalError
  end

  plugin_file = opts[:env_local] ? @local_file : @user_file

  if !plugin_file.has_plugin?(name)
    raise Errors::PluginNotInstalled,
      name: name
  end

  plugin_file.remove_plugin(name)

  # Clean the environment, removing any old plugins
  Vagrant::Bundler.instance.clean(installed_plugins)
rescue Gem::Exception => e
  raise Errors::BundlerError, message: e.to_s
end

#update_plugins(specific, **opts) ⇒ Object

Updates all or a specific set of plugins.


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
# File 'lib/vagrant/plugin/manager.rb', line 235

def update_plugins(specific, **opts)
  if opts[:env_local] && @local_file.nil?
    raise Errors::PluginNoLocalError
  end

  plugin_file = opts[:env_local] ? @local_file : @user_file

  result = Vagrant::Bundler.instance.update(plugin_file.installed_plugins, specific)
  plugin_file.installed_plugins.each do |name, info|
    matching_spec = result.detect{|s| s.name == name}
    info = Hash[
      info.map do |key, value|
        [key.to_sym, value]
      end
    ]
    if matching_spec
      plugin_file.add_plugin(name, **info.merge(
        version: "> 0",
        installed_gem_version: matching_spec.version.to_s
      ))
    end
  end
  Vagrant::Bundler.instance.clean(installed_plugins)
  result
rescue Gem::Exception => e
  raise Errors::BundlerError, message: e.to_s
end