Class: Chef::Provider::Package::Rubygems::GemEnvironment

Inherits:
Object
  • Object
show all
Defined in:
lib/chef/provider/package/rubygems.rb

Constant Summary collapse

DEFAULT_UNINSTALLER_OPTS =
{ ignore: true, executables: true }.freeze

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ GemEnvironment

Returns a new instance of GemEnvironment.

[View source]

48
49
50
51
52
53
54
# File 'lib/chef/provider/package/rubygems.rb', line 48

def initialize(*args)
  super
  # HACK: trigger gem config load early. Otherwise it can get lazy
  # loaded during operations where we've set Gem.sources to an
  # alternate value and overwrite it with the defaults.
  Gem.configuration
end

Instance Method Details

#candidate_version_from_file(gem_dependency, source) ⇒ Object

Determines the candidate version for a gem from a .gem file on disk and checks if it matches the version constraints in gem_dependency

Returns

Gem::Version a singular gem version object is returned if the gem

is available

nil returns nil if the gem on disk doesn’t match the

version constraints for +gem_dependency+
[View source]

155
156
157
158
159
160
161
162
163
164
165
# File 'lib/chef/provider/package/rubygems.rb', line 155

def candidate_version_from_file(gem_dependency, source)
  spec = spec_from_file(source)
  if spec.satisfies_requirement?(gem_dependency)
    logger.trace { "found candidate gem version #{spec.version} from local gem package #{source}" }
    spec.version
  else
    # This is probably going to end badly...
    logger.warn { "gem package #{source} does not satisfy the requirements #{gem_dependency}" }
    nil
  end
end

#candidate_version_from_remote(gem_dependency, *sources) ⇒ Object

Finds the newest version that satisfies the constraints of gem_dependency. The version is determined from the cache or a round-trip to the server as needed. The architecture and gem sources will be set before making the query.

Returns

Gem::Version a singular gem version object is returned if the gem

is available

nil returns nil if the gem could not be found

Raises:

  • (NotImplementedError)
[View source]

176
177
178
# File 'lib/chef/provider/package/rubygems.rb', line 176

def candidate_version_from_remote(gem_dependency, *sources)
  raise NotImplementedError
end

#dependency_installer(**opts) ⇒ Object

[View source]

247
248
249
# File 'lib/chef/provider/package/rubygems.rb', line 247

def dependency_installer(**opts)
  Gem::DependencyInstaller.new(**opts)
end

#find_newest_remote_version(gem_dependency, *sources) ⇒ Object

Find the newest gem version available from Gem.sources that satisfies the constraints of gem_dependency

[View source]

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
# File 'lib/chef/provider/package/rubygems.rb', line 183

def find_newest_remote_version(gem_dependency, *sources)
  spec, source =
    if Chef::Config[:rubygems_cache_enabled]
      # This code caches every gem on rubygems.org and uses lots of RAM
      available_gems = dependency_installer.find_gems_with_sources(gem_dependency)
      available_gems.pick_best!
      best_gem = available_gems.set.first
      best_gem && [best_gem.spec, best_gem.source]
    else
      # Use the API that 'gem install' calls which does not pull down the rubygems universe
      begin
        rs = dependency_installer.resolve_dependencies gem_dependency.name, gem_dependency.requirement
        rs.specs.find { |s| s.name == gem_dependency.name }
        # ruby-3.0.0 versions of rubygems-3.x throws NoMethodError when the dep is not found
      rescue Gem::UnsatisfiableDependencyError, NoMethodError
        nil
      end
    end

  version = spec && spec.version
  if version
    logger.trace { "found gem #{spec.name} version #{version} for platform #{spec.platform} from #{source}" }
    version
  else
    source_list = sources.compact.empty? ? "[#{Gem.sources.to_a.join(", ")}]" : "[#{sources.join(", ")}]"
    logger.warn { "failed to find gem #{gem_dependency} from #{source_list}" }
    nil
  end
end

#gem_pathsObject

The paths where rubygems should search for installed gems. Implemented by subclasses.

Raises:

  • (NotImplementedError)
[View source]

58
59
60
# File 'lib/chef/provider/package/rubygems.rb', line 58

def gem_paths
  raise NotImplementedError
end

#gem_source_indexGem::SourceIndex

A rubygems source index containing the list of gemspecs for all available gems in the gem installation. Implemented by subclasses

Returns:

  • (Gem::SourceIndex)

Raises:

  • (NotImplementedError)
[View source]

68
69
70
# File 'lib/chef/provider/package/rubygems.rb', line 68

def gem_source_index
  raise NotImplementedError
end

#gem_specificationGem::Specification

A rubygems specification object containing the list of gemspecs for all available gems in the gem installation. Implemented by subclasses

Returns:

  • (Gem::Specification)

Raises:

  • (NotImplementedError)
[View source]

78
79
80
# File 'lib/chef/provider/package/rubygems.rb', line 78

def gem_specification
  raise NotImplementedError
end

#install(gem_dependency, options = {}) ⇒ Object

Installs a gem via the rubygems ruby API.

Options

:sources rubygems servers to use Other options are passed to Gem::DependencyInstaller.new

[View source]

218
219
220
221
222
223
224
# File 'lib/chef/provider/package/rubygems.rb', line 218

def install(gem_dependency, options = {})
  with_gem_sources(*options.delete(:sources)) do
    with_correct_verbosity do
      dependency_installer(**options).install(gem_dependency)
    end
  end
end

#installed_versions(gem_dep) ⇒ Array<Gem::Specification>

Lists the installed versions of gem_name, constrained by the version spec in gem_dep

Parameters:

  • gem_dep (Gem::Dependency)

    the version specification that constrains which gems are used.

Returns:

  • (Array<Gem::Specification>)

    an array of Gem::Specification objects

[View source]

93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/chef/provider/package/rubygems.rb', line 93

def installed_versions(gem_dep)
  rubygems_version = Gem::Version.new(Gem::VERSION)
  if rubygems_version >= Gem::Version.new("3.1")
    # In newer Rubygems, bundler is now a "default gem" which means
    # even with AlternateGemEnvironment when you try to get the
    # installed versions, you get the one from Chef's Ruby's default
    # gems. This workaround ignores default gems entirely so we see
    # only the installed gems.
    stubs = gem_specification.send(:installed_stubs, gem_specification.dirs, "#{gem_dep.name}-*.gemspec")
    # Filter down to only to only stubs we actually want. The name
    # filter is needed in case of things like `foo-*.gemspec` also
    # matching a gem named `foo-bar`.
    stubs.select! { |stub| stub.name == gem_dep.name && gem_dep.requirement.satisfied_by?(stub.version) }
    # This isn't sorting before returning because the only code that
    # uses this method calls `max_by` so it doesn't need to be sorted.
    stubs
  else # >= rubygems 1.8 behavior
    gem_specification.find_all_by_name(gem_dep.name, gem_dep.requirement)
  end
end

#rubygems_versionObject

Raises:

  • (NotImplementedError)
[View source]

82
83
84
# File 'lib/chef/provider/package/rubygems.rb', line 82

def rubygems_version
  raise NotImplementedError
end

#spec_from_file(file) ⇒ Object

Extracts the gemspec from a (on-disk) gem package.

Returns

Gem::Specification

– Compatibility note: Rubygems 1.x uses Gem::Format, 2.0 moved this code into Gem::Package.

[View source]

135
136
137
138
139
140
141
142
143
144
145
# File 'lib/chef/provider/package/rubygems.rb', line 135

def spec_from_file(file)
  if defined?(Gem::Format) && Gem::Package.respond_to?(:open)
    Gem::Format.from_file_by_path(file).spec
  else
    # Gem::Package is getting defined as an empty class as of bundler 2.5.23
    # and therefore won't autoload
    # ["bundler-2.5.23/lib/bundler/rubygems_ext.rb", 457]
    require "rubygems/package" if Gem::Package.method(:new).source_location.nil?
    Gem::Package.new(file).spec
  end
end

#uninstall(gem_name, gem_version = nil, opts = {}) ⇒ Object

Uninstall the gem gem_name via the rubygems ruby API. If gem_version is provided, only that version will be uninstalled. Otherwise, all versions are uninstalled.

Options

Options are passed to Gem::Uninstaller.new

[View source]

232
233
234
235
236
237
# File 'lib/chef/provider/package/rubygems.rb', line 232

def uninstall(gem_name, gem_version = nil, opts = {})
  gem_version ? opts[:version] = gem_version : opts[:all] = true
  with_correct_verbosity do
    uninstaller(gem_name, **opts).uninstall
  end
end

#uninstaller(gem_name, **opts) ⇒ Object

[View source]

251
252
253
# File 'lib/chef/provider/package/rubygems.rb', line 251

def uninstaller(gem_name, **opts)
  Gem::Uninstaller.new(gem_name, **DEFAULT_UNINSTALLER_OPTS.merge(opts))
end

#with_correct_verbosityObject

Set rubygems’ user interaction to ConsoleUI or SilentUI depending on our current debug level

[View source]

242
243
244
245
# File 'lib/chef/provider/package/rubygems.rb', line 242

def with_correct_verbosity
  Gem::DefaultUserInteraction.ui = logger.trace? ? Gem::ConsoleUI.new : Gem::SilentUI.new
  yield
end

#with_gem_sources(*sources) ⇒ Object

Yields to the provided block with rubygems’ source list set to the list provided. Always resets the list when the block returns or raises an exception.

[View source]

118
119
120
121
122
123
124
125
# File 'lib/chef/provider/package/rubygems.rb', line 118

def with_gem_sources(*sources)
  sources.compact!
  original_sources = Gem.sources
  Gem.sources = sources unless sources.empty?
  yield
ensure
  Gem.sources = original_sources
end