class Gem::ConfigFile

Gem::ConfigFile RubyGems options and gem command options from gemrc.

gemrc is a YAML file that uses strings to match gem command arguments and symbols to match RubyGems options.

Gem command arguments use a String key that matches the command name and allow you to specify default arguments:

install: --no-rdoc --no-ri
update: --no-rdoc --no-ri

You can use gem: to set default arguments for all commands.

RubyGems options use symbol keys. Valid options are:

:backtrace

See backtrace

:bulk_threshold

See bulk_threshold

:cooldown

See cooldown

:verbose

See verbose

:update_sources

See update_sources

:concurrent_downloads

See concurrent_downloads

:cert_expiration_length_days

See cert_expiration_length_days

:install_extension_in_lib

See install_extension_in_lib

:ipv4_fallback_enabled

See ipv4_fallback_enabled

:global_gem_cache

See global_gem_cache

:use_psych

See use_psych

:credential_store

See credential_store

:gemhome

See home

:gempath

See path

:sources

Sets Gem::sources

:disable_default_gem_server

See disable_default_gem_server

:ssl_verify_mode

See ssl_verify_mode

:ssl_ca_cert

See ssl_ca_cert

:ssl_client_cert

See ssl_client_cert

gemrc files may exist in various locations and are read and merged in the following order:

  • system wide (/etc/gemrc)

  • per user (~/.gemrc)

  • per environment (gemrc files listed in the GEMRC environment variable)

Constants

CREDENTIAL_STORE_DEFAULT_ACCOUNT

The account name under which the default RubyGems.org API key is stored in the credential store, mirroring the :rubygems_api_key symbol used by the plain text credentials file.

DEFAULT_BACKTRACE
DEFAULT_BULK_THRESHOLD
DEFAULT_CERT_EXPIRATION_LENGTH_DAYS
DEFAULT_CONCURRENT_DOWNLOADS
DEFAULT_COOLDOWN
DEFAULT_CREDENTIAL_STORE
DEFAULT_GLOBAL_GEM_CACHE
DEFAULT_INSTALL_EXTENSION_IN_LIB
DEFAULT_IPV4_FALLBACK_ENABLED
DEFAULT_UPDATE_SOURCES
DEFAULT_USE_PSYCH
DEFAULT_VERBOSITY
OPERATING_SYSTEM_DEFAULTS

For Ruby packagers to set configuration defaults. Set in rubygems/defaults/operating_system.rb

PLATFORM_DEFAULTS

For Ruby implementers to set configuration defaults. Set in rubygems/defaults/#{RUBY_ENGINE}.rb

SYSTEM_WIDE_CONFIG_FILE

Attributes

List of arguments supplied to the config file object.

True if we print backtraces on errors.

Bulk threshold value. If the number of missing gems are above this threshold value, then a bulk download technique is used. (deprecated)

Expiration length to sign a certificate

Number of gem downloads that should be performed concurrently.

Number of days a newly published gem version must wait before it is considered for installation or update (the cooldown period). 0 disables the cooldown, unless Bundler is configured with a longer one: this setting and Bundler’s own cooldown setting are both read, and the longer of the two applies, so a cooldown configured for only one of the two tools still covers both. --cooldown overrides both.

A value that cannot be read as a non-negative integer warns and takes no part in that resolution, so a typo in the gemrc file does not make every command fail.

Experimental == ↑

Store and read push/authentication credentials in a credential store instead of the plain text credentials file. true selects the operating system’s native store (macOS Keychain, Linux Secret Service, Windows Credential Manager) when one is available on this platform. A string selects a named backend registered by a third-party gem, such as +“1password”+. false (the default) keeps using the credentials file.

True if we want to force specification of gem server when pushing a gem

Use a global cache for .gem files shared across all Ruby installations. When enabled, gems fetched from a remote source are cached to ~/.cache/gem/gems (or XDG_CACHE_HOME/gem/gems). Gems installed from a local path are not, since a cached copy is later reused without being verified again. gem fetch still writes to the working directory, and an unwritable cache directory falls back to the cache of the installation.

Where to install gems (deprecated)

Install extensions into lib as well as into the extension directory.

Experimental == ↑

Fallback to IPv4 when IPv6 is not reachable or slow (default: false)

Where to look for gems (deprecated)

sources to look for gems

Path name of directory or file of openssl CA certificate, used for remote https connection

Path name of directory or file of openssl client certificate, used for remote https connection with client authentication

openssl verify mode value, used for remote https connection

True if we want to update the SourceInfoCache every time, false otherwise

Use Psych (C extension YAML parser) instead of the pure Ruby YAMLSerializer.

Verbose level of output:

  • false – No output

  • true – Normal output

  • :loud – Extra output

Public Class Methods

Built on the same normalized form the credentials file uses, so the two never disagree about which host a spelling refers to. Userinfo is dropped because the account reaches the backend as a command argument, where any other user on the machine can read it.

# File lib/rubygems/config_file.rb, line 752
def self.credential_store_account(host)
  host = normalize_credentials_key(host).to_s
  return host unless host.match?(%r{\Ahttps?://}i)

  require_relative "vendor/uri/lib/uri"
  uri = Gem::URI(host)
  return host unless uri.userinfo

  uri = uri.dup
  uri.user = uri.password = nil
  uri.to_s
rescue Gem::URI::Error
  host
end
# File lib/rubygems/config_file.rb, line 775
def self.deep_transform_config_keys!(config)
  config.transform_keys! do |k|
    if k.match?(/\A:(.*)\z/)
      k[1..-1].to_sym
    elsif k.include?("__") || k.end_with?("/")
      if k.is_a?(Symbol)
        k.to_s.gsub(/__/,".").delete_suffix("/").to_sym
      else
        k.dup.gsub(/__/,".").delete_suffix("/")
      end
    else
      k
    end
  end

  config.transform_values! do |v|
    if v.is_a?(String)
      if v.match?(/\A:(.*)\z/)
        v[1..-1].to_sym
      elsif v.match?(/\A[+-]?\d+\z/)
        v.to_i
      elsif v.match?(/\A(?:true|false)\z/)
        v == "true"
      elsif v.empty?
        nil
      else
        v
      end
    elsif v.respond_to?(:empty?) && v.empty?
      nil
    elsif v.is_a?(Hash)
      deep_transform_config_keys!(v)
    else
      v
    end
  end

  config
end
# File lib/rubygems/config_file.rb, line 728
def self.dump_with_rubygems_yaml(content)
  content.transform_keys! do |k|
    k.is_a?(Symbol) ? ":#{k}" : k
  end

  require_relative "yaml_serializer"
  Gem::YAMLSerializer.dump(content)
end
# File lib/rubygems/config_file.rb, line 737
def self.load_with_rubygems_config_hash(yaml)
  require_relative "yaml_serializer"

  content = Gem::YAMLSerializer.load(yaml, permitted_classes: [])
  return {} unless content.is_a?(Hash)

  deep_transform_config_keys!(content)
end

Create the config file object. args is the list of arguments from the command line.

The following command line options are handled early here rather than later at the time most command options are processed.

--config-file, --config-file==NAME

Obviously these need to be handled by the ConfigFile object to ensure we get the right config file.

--backtrace

Backtrace needs to be turned on early so that errors before normal option parsing can be properly handled.

--debug

Enable Ruby level debug messages. Handled early for the same reason as –backtrace.

# File lib/rubygems/config_file.rb, line 250
def initialize(args)
  set_config_file_name(args)

  @backtrace = DEFAULT_BACKTRACE
  @bulk_threshold = DEFAULT_BULK_THRESHOLD
  @cooldown = DEFAULT_COOLDOWN
  @verbose = DEFAULT_VERBOSITY
  @update_sources = DEFAULT_UPDATE_SOURCES
  @concurrent_downloads = DEFAULT_CONCURRENT_DOWNLOADS
  @cert_expiration_length_days = DEFAULT_CERT_EXPIRATION_LENGTH_DAYS
  @install_extension_in_lib = DEFAULT_INSTALL_EXTENSION_IN_LIB
  @ipv4_fallback_enabled = ENV["IPV4_FALLBACK_ENABLED"] == "true" || DEFAULT_IPV4_FALLBACK_ENABLED
  @global_gem_cache = ENV["RUBYGEMS_GLOBAL_GEM_CACHE"] == "true" || DEFAULT_GLOBAL_GEM_CACHE
  @use_psych = ENV["RUBYGEMS_USE_PSYCH"] == "true" || DEFAULT_USE_PSYCH
  @credential_store = normalize_credential_store(ENV["RUBYGEMS_CREDENTIAL_STORE"], DEFAULT_CREDENTIAL_STORE)

  operating_system_config = Gem::Util.deep_dup(OPERATING_SYSTEM_DEFAULTS)
  platform_config = Gem::Util.deep_dup(PLATFORM_DEFAULTS)
  system_config = load_file SYSTEM_WIDE_CONFIG_FILE
  user_config = load_file config_file_name

  environment_config = (ENV["GEMRC"] || "").
    split(File::PATH_SEPARATOR).inject({}) do |result, file|
      result.merge load_file file
    end

  @hash = operating_system_config.merge platform_config
  unless args.index "--norc"
    @hash = @hash.merge system_config
    @hash = @hash.merge user_config
    @hash = @hash.merge environment_config
  end

  @hash.transform_keys! do |k|
    # gemhome and gempath are not working with symbol keys
    if %w[backtrace bulk_threshold cooldown verbose update_sources cert_expiration_length_days
          concurrent_downloads install_extension_in_lib ipv4_fallback_enabled
          global_gem_cache use_psych credential_store sources
          disable_default_gem_server ssl_verify_mode ssl_ca_cert ssl_client_cert].include?(k)
      k.to_sym
    else
      k
    end
  end

  # HACK: these override command-line args, which is bad
  @backtrace                   = @hash[:backtrace]                   if @hash.key? :backtrace
  @bulk_threshold              = @hash[:bulk_threshold]              if @hash.key? :bulk_threshold
  @cooldown                    = @hash[:cooldown]                    if @hash.key? :cooldown
  @verbose                     = @hash[:verbose]                     if @hash.key? :verbose
  @update_sources              = @hash[:update_sources]              if @hash.key? :update_sources
  @concurrent_downloads        = @hash[:concurrent_downloads]        if @hash.key? :concurrent_downloads
  @cert_expiration_length_days = @hash[:cert_expiration_length_days] if @hash.key? :cert_expiration_length_days
  @install_extension_in_lib    = @hash[:install_extension_in_lib]    if @hash.key? :install_extension_in_lib
  @ipv4_fallback_enabled       = @hash[:ipv4_fallback_enabled]       if @hash.key? :ipv4_fallback_enabled
  @global_gem_cache            = @hash[:global_gem_cache]            if @hash.key? :global_gem_cache
  @use_psych                   = @hash[:use_psych]                   if @hash.key? :use_psych
  @credential_store            = normalize_credential_store(@hash[:credential_store], @credential_store) if @hash.key? :credential_store

  @home                        = @hash[:gemhome]                     if @hash.key? :gemhome
  @path                        = @hash[:gempath]                     if @hash.key? :gempath
  @sources                     = @hash[:sources]                     if @hash.key? :sources
  @disable_default_gem_server  = @hash[:disable_default_gem_server]  if @hash.key? :disable_default_gem_server
  @ssl_verify_mode             = @hash[:ssl_verify_mode]             if @hash.key? :ssl_verify_mode
  @ssl_ca_cert                 = @hash[:ssl_ca_cert]                 if @hash.key? :ssl_ca_cert
  @ssl_client_cert             = @hash[:ssl_client_cert]             if @hash.key? :ssl_client_cert

  @api_keys         = nil
  @rubygems_api_key = nil

  handle_arguments args
end

A trailing slash, or the underscore pair standing in for a dot, comes back rewritten from load_file, so a raw host would silently miss.

# File lib/rubygems/config_file.rb, line 769
def self.normalize_credentials_key(host)
  return host unless host.is_a?(String)

  deep_transform_config_keys!(host => nil).keys.first
end

Public Instance Methods

Return the configuration information for key.

# File lib/rubygems/config_file.rb, line 707
def [](key)
  @hash[key] || @hash[key.to_s]
end

Set configuration option key to value.

# File lib/rubygems/config_file.rb, line 712
def []=(key, value)
  @hash[key] = value
end

Hash of RubyGems.org and alternate API keys, as they appear in the credentials file. Keys held in the credential store are not included, so this is not the full set of keys a command can authenticate with. Use credential_store_api_key_for or credential_store_default_api_key to reach those.

# File lib/rubygems/config_file.rb, line 330
def api_keys
  load_api_keys unless @api_keys

  @api_keys
end

True if the backtrace option has been specified, or debug is on.

# File lib/rubygems/config_file.rb, line 571
def backtrace
  @backtrace || $DEBUG
end

Checks the permissions of the credentials file. If they are not 0600 an error message is displayed and RubyGems aborts.

# File lib/rubygems/config_file.rb, line 340
  def check_credentials_permissions
    return if Gem.win_platform? # windows doesn't write 0600 as 0600
    return unless File.exist? credentials_path

    existing_permissions = File.stat(credentials_path).mode & 0o777

    return if existing_permissions == 0o600

    alert_error <<-ERROR
Your gem push credentials file located at:

\t#{credentials_path}

has file permissions of 0#{existing_permissions.to_s 8} but 0600 is required.

To fix this error run:

\tchmod 0600 #{credentials_path}

You should reset your credentials at:

\thttps://rubygems.org/profile/edit

if you believe they were disclosed to a third party.
    ERROR

    terminate_interaction 1
  end

The name of the configuration file.

# File lib/rubygems/config_file.rb, line 590
def config_file_name
  @config_file_name || Gem.config_file
end

Looks up host‘s own API key from the credential store, when the credential_store setting is on. Only the host-specific account is consulted: falling back to the default account here would send the RubyGems.org key to whatever host was asked for, ahead of that host’s own key in the credentials file. credential_store_default_api_key covers the default account, at the precedence the credentials file uses for it.

# File lib/rubygems/config_file.rb, line 440
def credential_store_api_key_for(host)
  return nil if host.nil? || host.to_s.empty?
  return nil unless credential_store
  return nil unless store = active_credential_store

  store.get(self.class.credential_store_account(host))
end

The default RubyGems.org API key from the credential store, or nil. This is the stored counterpart of rubygems_api_key, and belongs at the same point in the lookup order.

# File lib/rubygems/config_file.rb, line 471
def credential_store_default_api_key
  return nil unless credential_store
  return nil unless store = active_credential_store

  store.get(CREDENTIAL_STORE_DEFAULT_ACCOUNT)
end

True when a read for host failed rather than finding nothing. Whether the store holds a key for it is unknowable once the read fails, which is the point: a caller that would otherwise fall through to a key belonging to a different host has to treat “unknown” differently from “absent”.

# File lib/rubygems/config_file.rb, line 454
def credential_store_read_failed_for?(host)
  return false unless credential_store
  return false unless store = active_credential_store

  # #rubygems_api_key reads the default account on the way to answering, and
  # for the default host that is the only failure that can happen.
  return true if store.read_failed?(CREDENTIAL_STORE_DEFAULT_ACCOUNT)
  return false if host.nil? || host.to_s.empty?

  store.read_failed?(self.class.credential_store_account(host))
end

Location of RubyGems.org credentials

# File lib/rubygems/config_file.rb, line 372
def credentials_path
  credentials = File.join Gem.user_home, ".gem", "credentials"
  if File.exist? credentials
    credentials
  else
    File.join Gem.data_home, "gem", "credentials"
  end
end

Delegates to @hash

# File lib/rubygems/config_file.rb, line 614
def each(&block)
  hash = @hash.dup
  hash.delete :update_sources
  hash.delete :verbose
  hash.delete :backtrace
  hash.delete :bulk_threshold

  yield :update_sources, @update_sources
  yield :verbose, @verbose
  yield :backtrace, @backtrace
  yield :bulk_threshold, @bulk_threshold

  yield "config_file_name", @config_file_name if @config_file_name

  hash.each(&block)
end

Handle the command arguments.

# File lib/rubygems/config_file.rb, line 632
def handle_arguments(arg_list)
  @args = []

  arg_list.each do |arg|
    case arg
    when /^--(backtrace|traceback)$/ then
      @backtrace = true
    when /^--debug$/ then
      $DEBUG = true

      warn "NOTE:  Debugging mode prints all exceptions even when rescued"
    else
      @args << arg
    end
  end
end

Reads time of last update check from state file

# File lib/rubygems/config_file.rb, line 600
def last_update_check
  if File.readable?(state_file_name)
    File.read(state_file_name).to_i
  else
    0
  end
end

Writes time of last update check to state file

# File lib/rubygems/config_file.rb, line 609
def last_update_check=(timestamp)
  File.write(state_file_name, timestamp.to_s) if state_file_writable?
end
# File lib/rubygems/config_file.rb, line 381
def load_api_keys
  check_credentials_permissions

  @api_keys = if File.exist? credentials_path
    load_file(credentials_path)
  else
    @hash
  end

  if @api_keys.key? :rubygems_api_key
    @rubygems_api_key    = @api_keys[:rubygems_api_key]
    @api_keys[:rubygems] = @api_keys.delete :rubygems_api_key unless
      @api_keys.key? :rubygems
  end
end
# File lib/rubygems/config_file.rb, line 546
def load_file(filename)
  yaml_errors = [ArgumentError]

  return {} unless filename && !filename.empty? && File.exist?(filename)

  begin
    config = self.class.load_with_rubygems_config_hash(File.read(filename))
    has_invalid_keys = config.keys.any? {|k| k.to_s.gsub(%r{https?:\/\/}, "").include?(": ") }
    has_invalid_values = config.values.any? {|v| v.is_a?(String) && v.gsub(%r{https?:\/\/}, "").match?(/\A\S+: /) }
    if has_invalid_keys || has_invalid_values
      warn "Failed to load #{filename} because it doesn't contain valid YAML hash"
      return {}
    else
      return config
    end
  rescue *yaml_errors => e
    warn "Failed to load #{filename}, #{e}"
  rescue Errno::EACCES
    warn "Failed to load #{filename} due to permissions problem."
  end

  {}
end

Really verbose mode gives you extra output.

# File lib/rubygems/config_file.rb, line 650
def really_verbose
  case verbose
  when true, false, nil then
    false
  else
    true
  end
end

Returns the RubyGems.org API key

# File lib/rubygems/config_file.rb, line 400
def rubygems_api_key
  load_api_keys unless @rubygems_api_key

  # #load_api_keys only reads the credentials file, which no longer holds the
  # key once it is stored. A copy left there after the move is stale.
  credential_store_default_api_key || @rubygems_api_key
end

Sets the RubyGems.org API key to api_key

# File lib/rubygems/config_file.rb, line 411
def rubygems_api_key=(api_key)
  if credential_store
    store = active_credential_store

    if api_key.to_s.empty?
      warn_unremoved_credential(CREDENTIAL_STORE_DEFAULT_ACCOUNT) if store&.available? && !store.delete(CREDENTIAL_STORE_DEFAULT_ACCOUNT)
    elsif store&.set(CREDENTIAL_STORE_DEFAULT_ACCOUNT, api_key)
      remove_api_key_from_file(:rubygems_api_key)
      @rubygems_api_key = api_key
      return
    else
      warn_unremoved_credential(CREDENTIAL_STORE_DEFAULT_ACCOUNT) if store&.available? && !store.delete(CREDENTIAL_STORE_DEFAULT_ACCOUNT)
      warn_credential_store_fallback
    end
  end

  set_api_key :rubygems_api_key, api_key

  @rubygems_api_key = api_key
end

Set a specific host’s API key to api_key

# File lib/rubygems/config_file.rb, line 481
def set_api_key(host, api_key)
  if credential_store && host != :rubygems_api_key
    store = active_credential_store

    if api_key.to_s.empty?
      delete_stored_key(store, host)
    elsif store&.set(self.class.credential_store_account(host), api_key)
      remove_api_key_from_file(host)
      return
    else
      delete_stored_key(store, host)
      warn_credential_store_fallback
    end
  end

  check_credentials_permissions

  config = load_file(credentials_path).merge(self.class.normalize_credentials_key(host) => api_key)

  write_credentials(config)

  load_api_keys # reload
end

The name of the state file.

# File lib/rubygems/config_file.rb, line 595
def state_file_name
  Gem.state_file
end

Check state file is writable. Creates empty file if not present to ensure we can write to it.

# File lib/rubygems/config_file.rb, line 576
def state_file_writable?
  if File.exist?(state_file_name)
    File.writable?(state_file_name)
  else
    require "fileutils"
    FileUtils.mkdir_p File.dirname(state_file_name)
    File.open(state_file_name, "w") {}
    true
  end
rescue Errno::EACCES
  false
end

Remove the +~/.gem/credentials+ file to clear all the current sessions, and every RubyGems key from the credential store when the credential_store setting is on, including keys saved for other hosts with gem signin --host.

# File lib/rubygems/config_file.rb, line 511
def unset_api_key!
  store = active_credential_store
  store_cleared =
    if store.nil?
      true
    elsif store.available?
      store.delete_all
    else
      # Failing here would make signout exit 1 on every platform without a
      # native store, and plain success would claim a removal nobody made.
      Gem::CredentialStore.warn_once "The credential store is enabled but unavailable, so any key it holds was left in place."
      true
    end

  file_removed =
    if File.exist?(credentials_path)
      # POSIX deletes a read-only file whenever the directory is writable, so
      # the marking has to be honored explicitly.
      if File.writable?(credentials_path)
        begin
          File.delete(credentials_path)
          true
        rescue SystemCallError
          false
        end
      else
        false
      end
    else
      false
    end

  [store_cleared, file_removed]
end

Writes out this config file, replacing its source.

# File lib/rubygems/config_file.rb, line 697
def write
  require "fileutils"
  FileUtils.mkdir_p File.dirname(config_file_name)

  File.open config_file_name, "w" do |io|
    io.write to_yaml
  end
end