Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 3 additions & 142 deletions updater/lib/dependabot/updater.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@

require "dependabot/updater/error_handler"
require "dependabot/updater/operations"
require "dependabot/updater/security_update_helpers"
require "dependabot/security_advisory"
require "dependabot/update_checkers"
require "wildcard_matcher"

# rubocop:disable Metrics/ClassLength
module Dependabot
class Updater
# FIXME: Remove this once we deprecate the legacy_run code path
include SecurityUpdateHelpers
class SubprocessFailed < StandardError
attr_reader :raven_context

Expand Down Expand Up @@ -321,146 +323,6 @@ def raise_on_ignored?(dependency)
job.security_updates_only? || job.ignore_conditions_for(dependency).any?
end

def record_security_update_not_needed_error(checker)
Dependabot.logger.info(
"no security update needed as #{checker.dependency.name} " \
"is no longer vulnerable"
)

service.record_update_job_error(
error_type: "security_update_not_needed",
error_details: {
"dependency-name": checker.dependency.name
}
)
end

def record_security_update_ignored(checker)
Dependabot.logger.info(
"Dependabot cannot update to the required version as all versions " \
"were ignored for #{checker.dependency.name}"
)

service.record_update_job_error(
error_type: "all_versions_ignored",
error_details: {
"dependency-name": checker.dependency.name
}
)
end

def record_dependency_file_not_supported_error(checker)
Dependabot.logger.info(
"Dependabot can't update vulnerable dependencies for projects " \
"without a lockfile or pinned version requirement as the currently " \
"installed version of #{checker.dependency.name} isn't known."
)

service.record_update_job_error(
error_type: "dependency_file_not_supported",
error_details: {
"dependency-name": checker.dependency.name
}
)
end

def record_security_update_not_possible_error(checker)
latest_allowed_version =
(checker.lowest_resolvable_security_fix_version ||
checker.dependency.version)&.to_s
lowest_non_vulnerable_version =
checker.lowest_security_fix_version&.to_s
conflicting_dependencies = checker.conflicting_dependencies

Dependabot.logger.info(
security_update_not_possible_message(checker, latest_allowed_version,
conflicting_dependencies)
)
Dependabot.logger.info(earliest_fixed_version_message(lowest_non_vulnerable_version))

service.record_update_job_error(
error_type: "security_update_not_possible",
error_details: {
"dependency-name": checker.dependency.name,
"latest-resolvable-version": latest_allowed_version,
"lowest-non-vulnerable-version": lowest_non_vulnerable_version,
"conflicting-dependencies": conflicting_dependencies
}
)
end

def record_security_update_not_found(checker)
Dependabot.logger.info(
"Dependabot can't find a published or compatible non-vulnerable " \
"version for #{checker.dependency.name}. " \
"The latest available version is #{checker.dependency.version}"
)

service.record_update_job_error(
error_type: "security_update_not_found",
error_details: {
"dependency-name": checker.dependency.name,
"dependency-version": checker.dependency.version
},
dependency: checker.dependency
)
end

def record_pull_request_exists_for_latest_version(checker)
service.record_update_job_error(
error_type: "pull_request_exists_for_latest_version",
error_details: {
"dependency-name": checker.dependency.name,
"dependency-version": checker.latest_version&.to_s
},
dependency: checker.dependency
)
end

def record_pull_request_exists_for_security_update(existing_pull_request)
updated_dependencies = existing_pull_request.map do |dep|
{
"dependency-name": dep.fetch("dependency-name"),
"dependency-version": dep.fetch("dependency-version", nil),
"dependency-removed": dep.fetch("dependency-removed", nil)
}.compact
end

service.record_update_job_error(
error_type: "pull_request_exists_for_security_update",
error_details: {
"updated-dependencies": updated_dependencies
}
)
end

def earliest_fixed_version_message(lowest_non_vulnerable_version)
if lowest_non_vulnerable_version
"The earliest fixed version is #{lowest_non_vulnerable_version}."
else
"Dependabot could not find a non-vulnerable version"
end
end

def security_update_not_possible_message(checker, latest_allowed_version,
conflicting_dependencies)
if conflicting_dependencies.any?
dep_messages = conflicting_dependencies.map do |dep|
" #{dep['explanation']}"
end.join("\n")

dependencies_pluralized =
conflicting_dependencies.count > 1 ? "dependencies" : "dependency"

"The latest possible version that can be installed is " \
"#{latest_allowed_version} because of the following " \
"conflicting #{dependencies_pluralized}:\n\n#{dep_messages}"
else
"The latest possible version of #{checker.dependency.name} that can " \
"be installed is #{latest_allowed_version}"
end
end

def requirements_to_unlock(checker)
if job.lockfile_only? || !checker.requirements_unlocked_or_can_be?
if checker.can_update?(requirements_to_unlock: :none) then :none
Expand Down Expand Up @@ -625,4 +487,3 @@ def close_pull_request(reason:)
end
end
end
# rubocop:enable Metrics/ClassLength
4 changes: 3 additions & 1 deletion updater/lib/dependabot/updater/operations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require "dependabot/updater/operations/create_security_update_pull_request"
require "dependabot/updater/operations/group_update_all_versions"
require "dependabot/updater/operations/refresh_security_update_pull_request"
require "dependabot/updater/operations/refresh_version_update_pull_request"
require "dependabot/updater/operations/update_all_versions"

Expand All @@ -27,9 +28,10 @@ module Operations
# that does, so these Operations should be ordered so that those with most
# specific preconditions go before those with more permissive checks.
OPERATIONS = [
GroupUpdateAllVersions,
CreateSecurityUpdatePullRequest,
RefreshSecurityUpdatePullRequest,
RefreshVersionUpdatePullRequest,
GroupUpdateAllVersions,
UpdateAllVersions
]

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
# frozen_string_literal: true

require "dependabot/updater/security_update_helpers"

# This class implements our strategy for updating a single, insecure dependency
# to a secure version. We attempt to make the smallest version update possible,
# i.e. semver patch-level increase is preferred over minor-level increase.
module Dependabot
class Updater
module Operations
class CreateSecurityUpdatePullRequest
include SecurityUpdateHelpers

def self.applies_to?(job:)
return false if job.updating_a_pull_request?
# If we haven't been given data for the vulnerable dependency,
Expand Down Expand Up @@ -212,148 +216,6 @@ def existing_pull_request(updated_dependencies)
created_pull_requests.find { |pr| Set.new(pr) == new_pr_set }
end

### BEGIN: Security Update Helpers
def record_security_update_not_needed_error(checker)
Dependabot.logger.info(
"no security update needed as #{checker.dependency.name} " \
"is no longer vulnerable"
)

service.record_update_job_error(
error_type: "security_update_not_needed",
error_details: {
"dependency-name": checker.dependency.name
}
)
end

def record_security_update_ignored(checker)
Dependabot.logger.info(
"Dependabot cannot update to the required version as all versions " \
"were ignored for #{checker.dependency.name}"
)

service.record_update_job_error(
error_type: "all_versions_ignored",
error_details: {
"dependency-name": checker.dependency.name
}
)
end

def record_dependency_file_not_supported_error(checker)
Dependabot.logger.info(
"Dependabot can't update vulnerable dependencies for projects " \
"without a lockfile or pinned version requirement as the currently " \
"installed version of #{checker.dependency.name} isn't known."
)

service.record_update_job_error(
error_type: "dependency_file_not_supported",
error_details: {
"dependency-name": checker.dependency.name
}
)
end

def record_security_update_not_possible_error(checker)
latest_allowed_version =
(checker.lowest_resolvable_security_fix_version ||
checker.dependency.version)&.to_s
lowest_non_vulnerable_version =
checker.lowest_security_fix_version&.to_s
conflicting_dependencies = checker.conflicting_dependencies

Dependabot.logger.info(
security_update_not_possible_message(checker, latest_allowed_version,
conflicting_dependencies)
)
Dependabot.logger.info(earliest_fixed_version_message(lowest_non_vulnerable_version))

service.record_update_job_error(
error_type: "security_update_not_possible",
error_details: {
"dependency-name": checker.dependency.name,
"latest-resolvable-version": latest_allowed_version,
"lowest-non-vulnerable-version": lowest_non_vulnerable_version,
"conflicting-dependencies": conflicting_dependencies
}
)
end

def record_security_update_not_found(checker)
Dependabot.logger.info(
"Dependabot can't find a published or compatible non-vulnerable " \
"version for #{checker.dependency.name}. " \
"The latest available version is #{checker.dependency.version}"
)

service.record_update_job_error(
error_type: "security_update_not_found",
error_details: {
"dependency-name": checker.dependency.name,
"dependency-version": checker.dependency.version
},
dependency: checker.dependency
)
end

def record_pull_request_exists_for_latest_version(checker)
service.record_update_job_error(
error_type: "pull_request_exists_for_latest_version",
error_details: {
"dependency-name": checker.dependency.name,
"dependency-version": checker.latest_version&.to_s
},
dependency: checker.dependency
)
end

def record_pull_request_exists_for_security_update(existing_pull_request)
updated_dependencies = existing_pull_request.map do |dep|
{
"dependency-name": dep.fetch("dependency-name"),
"dependency-version": dep.fetch("dependency-version", nil),
"dependency-removed": dep.fetch("dependency-removed", nil)
}.compact
end

service.record_update_job_error(
error_type: "pull_request_exists_for_security_update",
error_details: {
"updated-dependencies": updated_dependencies
}
)
end

def earliest_fixed_version_message(lowest_non_vulnerable_version)
if lowest_non_vulnerable_version
"The earliest fixed version is #{lowest_non_vulnerable_version}."
else
"Dependabot could not find a non-vulnerable version"
end
end

def security_update_not_possible_message(checker, latest_allowed_version,
conflicting_dependencies)
if conflicting_dependencies.any?
dep_messages = conflicting_dependencies.map do |dep|
" #{dep['explanation']}"
end.join("\n")

dependencies_pluralized =
conflicting_dependencies.count > 1 ? "dependencies" : "dependency"

"The latest possible version that can be installed is " \
"#{latest_allowed_version} because of the following " \
"conflicting #{dependencies_pluralized}:\n\n#{dep_messages}"
else
"The latest possible version of #{checker.dependency.name} that can " \
"be installed is #{latest_allowed_version}"
end
end
### END: Security Update Helpers

def requirements_to_unlock(checker)
if job.lockfile_only? || !checker.requirements_unlocked_or_can_be?
if checker.can_update?(requirements_to_unlock: :none) then :none
Expand Down
Loading