class Dependabot::Hex::UpdateChecker::VersionResolver

Attributes

credentials[R]
dependency[R]
original_dependency_files[R]
prepared_dependency_files[R]

Public Class Methods

new(dependency:, credentials:, original_dependency_files:, prepared_dependency_files:) click to toggle source
# File lib/dependabot/hex/update_checker/version_resolver.rb, line 14
def initialize(dependency:, credentials:,
               original_dependency_files:, prepared_dependency_files:)
  @dependency = dependency
  @original_dependency_files = original_dependency_files
  @prepared_dependency_files = prepared_dependency_files
  @credentials = credentials
end

Public Instance Methods

latest_resolvable_version() click to toggle source
# File lib/dependabot/hex/update_checker/version_resolver.rb, line 22
def latest_resolvable_version
  @latest_resolvable_version ||= fetch_latest_resolvable_version
end

Private Instance Methods

check_original_requirements_resolvable() click to toggle source
# File lib/dependabot/hex/update_checker/version_resolver.rb, line 111
def check_original_requirements_resolvable
  SharedHelpers.in_a_temporary_directory do
    write_temporary_sanitized_dependency_files(prepared: false)
    FileUtils.cp(
      elixir_helper_check_update_path,
      "check_update.exs"
    )

    SharedHelpers.with_git_configured(credentials: credentials) do
      run_elixir_update_checker
    end
  end

  true
rescue SharedHelpers::HelperSubprocessFailed => e
  # TODO: Catch the warnings as part of the Elixir module. This happens
  # when elixir throws warnings from the manifest files that end up in
  # stdout and cause run_helper_subprocess to fail parsing the result as
  # JSON.
  return error_result(e) if includes_result?(e)

  raise Dependabot::DependencyFileNotResolvable, e.message
end
elixir_helper_check_update_path() click to toggle source
# File lib/dependabot/hex/update_checker/version_resolver.rb, line 170
def elixir_helper_check_update_path
  File.join(NativeHelpers.hex_helpers_dir, "lib/check_update.exs")
end
elixir_helper_path() click to toggle source
# File lib/dependabot/hex/update_checker/version_resolver.rb, line 166
def elixir_helper_path
  File.join(NativeHelpers.hex_helpers_dir, "lib/run.exs")
end
error_result(error) click to toggle source
# File lib/dependabot/hex/update_checker/version_resolver.rb, line 92
def error_result(error)
  return false unless includes_result?(error)

  result_json = error.message&.split("\n")&.last
  result = JSON.parse(result_json)["result"]
  return version_class.new(result) if version_class.correct?(result)

  result
end
fetch_latest_resolvable_version() click to toggle source
# File lib/dependabot/hex/update_checker/version_resolver.rb, line 31
def fetch_latest_resolvable_version
  latest_resolvable_version =
    SharedHelpers.in_a_temporary_directory do
      write_temporary_sanitized_dependency_files
      FileUtils.cp(
        elixir_helper_check_update_path,
        "check_update.exs"
      )

      SharedHelpers.with_git_configured(credentials: credentials) do
        run_elixir_update_checker
      end
    end

  return if latest_resolvable_version.nil?
  return latest_resolvable_version if latest_resolvable_version.match?(/^[0-9a-f]{40}$/)

  version_class.new(latest_resolvable_version)
rescue SharedHelpers::HelperSubprocessFailed => e
  handle_hex_errors(e)
end
handle_hex_errors(error) click to toggle source
# File lib/dependabot/hex/update_checker/version_resolver.rb, line 65
def handle_hex_errors(error)
  if error.message.include?("No authenticated organization found")
    org = error.message.match(/found for ([a-z_]+)\./).captures.first
    raise Dependabot::PrivateSourceAuthenticationFailure, org
  end

  if error.message.include?("Failed to fetch record for")
    org_match = error.message.match(%r{for 'hexpm:([a-z_]+)/})
    org = org_match&.captures&.first
    raise Dependabot::PrivateSourceAuthenticationFailure, org if org
  end

  # TODO: Catch the warnings as part of the Elixir module. This happens
  # when elixir throws warnings from the manifest files that end up in
  # stdout and cause run_helper_subprocess to fail parsing the result as
  # JSON.
  return error_result(error) if includes_result?(error)

  # Ignore dependencies which don't resolve due to mis-matching
  # environment specifications.
  # TODO: Update the environment specifications instead
  return if error.message.include?("Dependencies have diverged")

  check_original_requirements_resolvable
  raise error
end
includes_result?(error) click to toggle source
# File lib/dependabot/hex/update_checker/version_resolver.rb, line 102
def includes_result?(error)
  result = error.message&.split("\n")&.last
  return false unless result

  JSON.parse(error.message&.split("\n")&.last).key?("result")
rescue JSON::ParserError
  false
end
mix_env() click to toggle source
# File lib/dependabot/hex/update_checker/version_resolver.rb, line 157
def mix_env
  {
    "MIX_EXS" => File.join(NativeHelpers.hex_helpers_dir, "mix.exs"),
    "MIX_LOCK" => File.join(NativeHelpers.hex_helpers_dir, "mix.lock"),
    "MIX_DEPS" => File.join(NativeHelpers.hex_helpers_dir, "deps"),
    "MIX_QUIET" => "1"
  }
end
organization_credentials() click to toggle source
# File lib/dependabot/hex/update_checker/version_resolver.rb, line 174
def organization_credentials
  credentials.
    select { |cred| cred["type"] == "hex_organization" }.
    flat_map { |cred| [cred["organization"], cred.fetch("token", "")] }
end
run_elixir_update_checker() click to toggle source
# File lib/dependabot/hex/update_checker/version_resolver.rb, line 53
def run_elixir_update_checker
  SharedHelpers.run_helper_subprocess(
    env: mix_env,
    command: "mix run #{elixir_helper_path}",
    function: "get_latest_resolvable_version",
    args: [Dir.pwd,
           dependency.name,
           organization_credentials],
    stderr_to_stdout: true
  )
end
sanitize_mixfile(content) click to toggle source
# File lib/dependabot/hex/update_checker/version_resolver.rb, line 147
def sanitize_mixfile(content)
  Hex::FileUpdater::MixfileSanitizer.new(
    mixfile_content: content
  ).sanitized_content
end
version_class() click to toggle source
# File lib/dependabot/hex/update_checker/version_resolver.rb, line 153
def version_class
  Hex::Version
end
write_temporary_sanitized_dependency_files(prepared: true) click to toggle source
# File lib/dependabot/hex/update_checker/version_resolver.rb, line 135
def write_temporary_sanitized_dependency_files(prepared: true)
  files = if prepared then prepared_dependency_files
          else original_dependency_files
          end

  files.each do |file|
    path = file.name
    FileUtils.mkdir_p(Pathname.new(path).dirname)
    File.write(path, sanitize_mixfile(file.content))
  end
end