Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strict type github_actions #10156

Merged
merged 4 commits into from
Jul 8, 2024
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
8 changes: 4 additions & 4 deletions common/lib/dependabot/git_commit_checker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -144,14 +144,14 @@ def local_tag_for_latest_version
max_local_tag(allowed_version_tags)
end

sig { returns(T::Array[T.nilable(T::Hash[Symbol, T.untyped])]) }
sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
def local_tags_for_allowed_versions_matching_existing_precision
select_matching_existing_precision(allowed_version_tags).map { |t| to_local_tag(t) }
select_matching_existing_precision(allowed_version_tags).filter_map { |t| to_local_tag(t) }
end

sig { returns(T::Array[T.nilable(T::Hash[Symbol, T.untyped])]) }
sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
def local_tags_for_allowed_versions
allowed_version_tags.map { |t| to_local_tag(t) }
allowed_version_tags.filter_map { |t| to_local_tag(t) }
end

sig { returns(T::Array[Dependabot::GitRef]) }
Expand Down
4 changes: 2 additions & 2 deletions common/lib/dependabot/update_checkers/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def latest_resolvable_version

# Lowest available security fix version not checking resolvability
# @return [Dependabot::<package manager>::Version, #to_s] version class
sig { overridable.returns(Dependabot::Version) }
sig { overridable.returns(T.nilable(Dependabot::Version)) }
def lowest_security_fix_version
raise NotImplementedError, "#{self.class} must implement #lowest_security_fix_version"
end
Expand Down Expand Up @@ -363,7 +363,7 @@ def requirements_up_to_date?
end

# TODO: Should this return Dependabot::Version?
sig { returns(T.nilable(Gem::Version)) }
sig { returns(T.nilable(Dependabot::Version)) }
def current_version
@current_version ||=
T.let(
Expand Down
3 changes: 3 additions & 0 deletions github_actions/.rubocop.yml
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
inherit_from: ../.rubocop.yml

Sorbet/StrictSigil:
Enabled: true
141 changes: 99 additions & 42 deletions github_actions/lib/dependabot/github_actions/update_checker.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# typed: true
# typed: strict
# frozen_string_literal: true

require "sorbet-runtime"
Expand All @@ -15,29 +15,41 @@ module GithubActions
class UpdateChecker < Dependabot::UpdateCheckers::Base
extend T::Sig

sig { override.returns(T.nilable(T.any(String, Gem::Version))) }
def latest_version
@latest_version ||= fetch_latest_version
@latest_version ||= T.let(
fetch_latest_version,
T.nilable(T.any(String, Gem::Version))
)
end

sig { override.returns(T.nilable(T.any(String, Gem::Version))) }
def latest_resolvable_version
# Resolvability isn't an issue for GitHub Actions.
latest_version
end

sig { override.returns(T.nilable(T.any(String, Dependabot::Version))) }
def latest_resolvable_version_with_no_unlock
# No concept of "unlocking" for GitHub Actions (since no lockfile)
dependency.version
end

sig { override.returns(T.nilable(Dependabot::Version)) }
def lowest_security_fix_version
@lowest_security_fix_version ||= fetch_lowest_security_fix_version
@lowest_security_fix_version ||= T.let(
fetch_lowest_security_fix_version,
T.nilable(Dependabot::Version)
)
end

sig { override.returns(T.nilable(Dependabot::Version)) }
def lowest_resolvable_security_fix_version
# Resolvability isn't an issue for GitHub Actions.
lowest_security_fix_version
end

sig { override.returns(T::Array[T::Hash[Symbol, T.untyped]]) }
def updated_requirements
dependency.requirements.map do |req|
source = req[:source]
Expand All @@ -61,42 +73,47 @@ def updated_requirements

private

sig { returns(T::Array[Dependabot::SecurityAdvisory]) }
def active_advisories
security_advisories.select do |advisory|
advisory.vulnerable?(version_class.new(git_commit_checker.most_specific_tag_equivalent_to_pinned_ref))
end
end

sig { override.returns(T::Boolean) }
def latest_version_resolvable_with_full_unlock?
# Full unlock checks aren't relevant for GitHub Actions
false
end

sig { override.returns(T::Array[Dependabot::Dependency]) }
def updated_dependencies_after_full_unlock
raise NotImplementedError
end

sig { returns(T.nilable(T.any(Dependabot::Version, String))) }
def fetch_latest_version
# TODO: Support Docker sources
return unless git_dependency?

fetch_latest_version_for_git_dependency
end

sig { returns(T.nilable(T.any(Dependabot::Version, String))) }
def fetch_latest_version_for_git_dependency
return current_commit unless git_commit_checker.pinned?

# If the dependency is pinned to a tag that looks like a version then
# we want to update that tag.
if git_commit_checker.pinned_ref_looks_like_version? && latest_version_tag
latest_version = latest_version_tag.fetch(:version)
latest_version = latest_version_tag&.fetch(:version)
return current_version if shortened_semver_eq?(dependency.version, latest_version.to_s)

return latest_version
end

if git_commit_checker.pinned_ref_looks_like_commit_sha? && latest_version_tag
latest_version = latest_version_tag.fetch(:version)
latest_version = latest_version_tag&.fetch(:version)
return latest_commit_for_pinned_ref unless git_commit_checker.local_tag_for_pinned_sha

return latest_version
Expand All @@ -107,78 +124,105 @@ def fetch_latest_version_for_git_dependency
nil
end

sig { returns(T.nilable(Dependabot::Version)) }
def fetch_lowest_security_fix_version
# TODO: Support Docker sources
return unless git_dependency?

fetch_lowest_security_fix_version_for_git_dependency
end

sig { returns(T.nilable(Dependabot::Version)) }
def fetch_lowest_security_fix_version_for_git_dependency
lowest_security_fix_version_tag.fetch(:version)
lowest_security_fix_version_tag&.fetch(:version)
end

sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
def lowest_security_fix_version_tag
@lowest_security_fix_version_tag ||= begin
tags_matching_precision = git_commit_checker.local_tags_for_allowed_versions_matching_existing_precision
lowest_fixed_version = find_lowest_secure_version(tags_matching_precision)
if lowest_fixed_version
lowest_fixed_version
else
tags = git_commit_checker.local_tags_for_allowed_versions
find_lowest_secure_version(tags)
end
end
@lowest_security_fix_version_tag ||= T.let(
begin
tags_matching_precision = git_commit_checker.local_tags_for_allowed_versions_matching_existing_precision
lowest_fixed_version = find_lowest_secure_version(tags_matching_precision)
if lowest_fixed_version
lowest_fixed_version
else
tags = git_commit_checker.local_tags_for_allowed_versions
find_lowest_secure_version(tags)
end
end,
T.nilable(T::Hash[Symbol, String])
)
end

sig do
params(
tags: T::Array[T::Hash[Symbol, T.untyped]]
)
.returns(T.nilable(T::Hash[Symbol, T.untyped]))
end
def find_lowest_secure_version(tags)
relevant_tags = Dependabot::UpdateCheckers::VersionFilters.filter_vulnerable_versions(tags, security_advisories)
relevant_tags = filter_lower_tags(relevant_tags)

relevant_tags.min_by { |tag| tag.fetch(:version) }
end

sig { returns(T.nilable(String)) }
def latest_commit_for_pinned_ref
@latest_commit_for_pinned_ref ||= begin
head_commit_for_ref_sha = git_commit_checker.head_commit_for_pinned_ref
if head_commit_for_ref_sha
head_commit_for_ref_sha
else
url = git_commit_checker.dependency_source_details[:url]
source = T.must(Source.from_url(url))

SharedHelpers.in_a_temporary_directory(File.dirname(source.repo)) do |temp_dir|
repo_contents_path = File.join(temp_dir, File.basename(source.repo))

SharedHelpers.run_shell_command("git clone --no-recurse-submodules #{url} #{repo_contents_path}")

Dir.chdir(repo_contents_path) do
ref_branch = find_container_branch(git_commit_checker.dependency_source_details[:ref])
git_commit_checker.head_commit_for_local_branch(ref_branch) if ref_branch
@latest_commit_for_pinned_ref ||= T.let(
begin
head_commit_for_ref_sha = git_commit_checker.head_commit_for_pinned_ref
if head_commit_for_ref_sha
head_commit_for_ref_sha
else
url = git_commit_checker.dependency_source_details&.fetch(:url)
source = T.must(Source.from_url(url))

SharedHelpers.in_a_temporary_directory(File.dirname(source.repo)) do |temp_dir|
repo_contents_path = File.join(temp_dir, File.basename(source.repo))

SharedHelpers.run_shell_command("git clone --no-recurse-submodules #{url} #{repo_contents_path}")

Dir.chdir(repo_contents_path) do
ref_branch = find_container_branch(git_commit_checker.dependency_source_details&.fetch(:ref))
git_commit_checker.head_commit_for_local_branch(ref_branch) if ref_branch
end
end
end
end
end
end,
T.nilable(String)
)
end

sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
def latest_version_tag
@latest_version_tag ||= begin
return git_commit_checker.local_tag_for_latest_version if dependency.version.nil?
@latest_version_tag ||= T.let(
begin
return git_commit_checker.local_tag_for_latest_version if dependency.version.nil?

ref = git_commit_checker.local_ref_for_latest_version_matching_existing_precision
return ref if ref && ref.fetch(:version) > current_version
ref = git_commit_checker.local_ref_for_latest_version_matching_existing_precision
return ref if ref && ref.fetch(:version) > current_version

git_commit_checker.local_ref_for_latest_version_lower_precision
end
git_commit_checker.local_ref_for_latest_version_lower_precision
end,
T.nilable(T::Hash[Symbol, T.untyped])
)
end

sig do
params(
tags_array: T::Array[T::Hash[Symbol, T.untyped]]
)
.returns(T::Array[T::Hash[Symbol, T.untyped]])
end
def filter_lower_tags(tags_array)
return tags_array unless current_version

tags_array
.select { |tag| tag.fetch(:version) > current_version }
end

sig { params(source: T.nilable(T::Hash[Symbol, String])).returns(T.nilable(String)) }
def updated_ref(source)
# TODO: Support Docker sources
return unless git_dependency?
Expand Down Expand Up @@ -206,6 +250,7 @@ def updated_ref(source)
nil
end

sig { returns(T.nilable(String)) }
def latest_commit_sha
new_tag = latest_version_tag
return unless new_tag
Expand All @@ -217,20 +262,30 @@ def latest_commit_sha
end
end

sig { returns(T.nilable(String)) }
def current_commit
git_commit_checker.head_commit_for_current_branch
end

sig { returns(T::Boolean) }
def git_dependency?
git_commit_checker.git_dependency?
end

sig { returns(Dependabot::GitCommitChecker) }
def git_commit_checker
@git_commit_checker ||= git_commit_checker_for(nil)
@git_commit_checker ||= T.let(
git_commit_checker_for(nil),
T.nilable(Dependabot::GitCommitChecker)
)
end

sig { params(source: T.nilable(T::Hash[Symbol, String])).returns(Dependabot::GitCommitChecker) }
def git_commit_checker_for(source)
@git_commit_checkers ||= {}
@git_commit_checkers ||= T.let(
{},
T.nilable(T::Hash[T.nilable(T::Hash[Symbol, String]), Dependabot::GitCommitChecker])
)

@git_commit_checkers[source] ||= Dependabot::GitCommitChecker.new(
dependency: dependency,
Expand All @@ -242,6 +297,7 @@ def git_commit_checker_for(source)
)
end

sig { params(base: T.nilable(String), other: String).returns(T::Boolean) }
def shortened_semver_eq?(base, other)
return false unless base

Expand All @@ -252,6 +308,7 @@ def shortened_semver_eq?(base, other)
other_split[0..base_split.length - 1] == base_split
end

sig { params(sha: String).returns(T.nilable(String)) }
def find_container_branch(sha)
branches_including_ref = SharedHelpers.run_shell_command(
"git branch --remotes --contains #{sha}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -606,9 +606,13 @@
describe "#lowest_resolvable_security_fix_version" do
subject(:lowest_resolvable_security_fix_version) { checker.lowest_resolvable_security_fix_version }

before { allow(checker).to receive(:lowest_security_fix_version).and_return("delegate") }
before do
allow(checker)
.to receive(:lowest_security_fix_version)
.and_return(Dependabot::GithubActions::Version.new("2.0.0"))
end

it { is_expected.to eq("delegate") }
it { is_expected.to eq(Dependabot::GithubActions::Version.new("2.0.0")) }
end

describe "#updated_requirements" do
Expand Down
Loading