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.



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+



160
161
162
163
164
165
166
167
168
169
170
# File 'lib/chef/provider/package/rubygems.rb', line 160

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)


181
182
183
# File 'lib/chef/provider/package/rubygems.rb', line 181

def candidate_version_from_remote(gem_dependency, *sources)
  raise NotImplementedError
end

#dependency_installer(**opts) ⇒ Object



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

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+



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

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)


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)


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)


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



223
224
225
226
227
228
229
# File 'lib/chef/provider/package/rubygems.rb', line 223

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



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# 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.5.11")
    # The API changed as of rubygems 3.5.11
    stubs = gem_specification_record.send(:installed_stubs, "#{gem_dep.name}-*.gemspec")
    stubs.select! { |stub| stub.name == gem_dep.name && gem_dep.requirement.satisfied_by?(stub.version) }
    stubs
  elsif 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)


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.



140
141
142
143
144
145
146
147
148
149
150
# File 'lib/chef/provider/package/rubygems.rb', line 140

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



237
238
239
240
241
242
# File 'lib/chef/provider/package/rubygems.rb', line 237

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



256
257
258
# File 'lib/chef/provider/package/rubygems.rb', line 256

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



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

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.



123
124
125
126
127
128
129
130
# File 'lib/chef/provider/package/rubygems.rb', line 123

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