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)


38
39
40
41
42
43
44
45
46
47
48
# File 'lib/vagrant/plugin/manager.rb', line 38

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.



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

def local_file
  @local_file
end

#system_fileObject (readonly)

Returns the value of attribute system_file.



34
35
36
# File 'lib/vagrant/plugin/manager.rb', line 34

def system_file
  @system_file
end

#user_fileObject (readonly)

Returns the value of attribute user_file.



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

def user_file
  @user_file
end

Class Method Details

.instanceObject



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

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

.system_plugins_fileObject

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



23
24
25
26
27
# File 'lib/vagrant/plugin/manager.rb', line 23

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)


18
19
20
# File 'lib/vagrant/plugin/manager.rb', line 18

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)


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

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



53
54
55
56
57
58
59
# File 'lib/vagrant/plugin/manager.rb', line 53

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:



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

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)


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

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:



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

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)


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

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



65
66
67
68
69
70
71
72
73
74
75
# File 'lib/vagrant/plugin/manager.rb', line 65

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)


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

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



78
79
80
# File 'lib/vagrant/plugin/manager.rb', line 78

def ready?
  @globalized && @localized
end

#uninstall_plugin(name, **opts) ⇒ Object

Uninstalls the plugin with the given name.

Parameters:

  • name (String)


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

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.



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

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