From eb59dc61140edf59c41e746e611a83ce4a7df78d Mon Sep 17 00:00:00 2001 From: Jurre Stender Date: Mon, 22 Apr 2024 10:30:03 +0200 Subject: [PATCH 01/11] Improve handling for hashing unknown packages --- python/helpers/lib/hasher.py | 19 +++++++----- .../file_updater/pip_compile_file_updater.rb | 1 + .../file_updater/requirement_replacer.rb | 12 ++++++-- .../requirement_file_updater_spec.rb | 30 +++++++++++++++++++ .../file_updater/requirement_replacer_spec.rb | 14 +++++++++ .../requirements/hashes_unknown_package.txt | 9 ++++++ 6 files changed, 76 insertions(+), 9 deletions(-) create mode 100644 python/spec/fixtures/requirements/hashes_unknown_package.txt diff --git a/python/helpers/lib/hasher.py b/python/helpers/lib/hasher.py index 7e105c18c4..b6e01aa29d 100644 --- a/python/helpers/lib/hasher.py +++ b/python/helpers/lib/hasher.py @@ -1,17 +1,22 @@ import hashin import json import plette +import traceback from poetry.factory import Factory def get_dependency_hash(dependency_name, dependency_version, algorithm): - hashes = hashin.get_package_hashes( - dependency_name, - version=dependency_version, - algorithm=algorithm - ) - - return json.dumps({"result": hashes["hashes"]}) + try: + hashes = hashin.get_package_hashes( + dependency_name, + version=dependency_version, + algorithm=algorithm + ) + return json.dumps({"result": hashes["hashes"]}) + except hashin.PackageNotFoundError as e: + return json.dumps({"error": repr(e), "error_class:" : + e.__class__.__name__, "trace:": + ''.join(traceback.format_stack())}) def get_pipfile_hash(directory): diff --git a/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb b/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb index ea315fb10f..6e6fbc96d6 100644 --- a/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb +++ b/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb @@ -261,6 +261,7 @@ def freeze_dependency_requirement(file) return file.content unless old_req return file.content if old_req == "==#{dependency.version}" + debugger RequirementReplacer.new( content: file.content, dependency_name: dependency.name, diff --git a/python/lib/dependabot/python/file_updater/requirement_replacer.rb b/python/lib/dependabot/python/file_updater/requirement_replacer.rb index 048fbff8aa..8eebf0e0e9 100644 --- a/python/lib/dependabot/python/file_updater/requirement_replacer.rb +++ b/python/lib/dependabot/python/file_updater/requirement_replacer.rb @@ -12,6 +12,8 @@ module Dependabot module Python class FileUpdater class RequirementReplacer + PACKAGE_NOT_FOUND_ERROR = "PackageNotFoundError" + def initialize(content:, dependency_name:, old_requirement:, new_requirement:, new_hash_version: nil) @content = content @@ -137,11 +139,17 @@ def hash_separator(requirement) end def package_hashes_for(name:, version:, algorithm:) - SharedHelpers.run_helper_subprocess( + result = SharedHelpers.run_helper_subprocess( command: "pyenv exec python3 #{NativeHelpers.python_helper_path}", function: "get_dependency_hash", args: [name, version, algorithm] - ).map { |h| "--hash=#{algorithm}:#{h['hash']}" } + ) + + if result["error_class"] == PACKAGE_NOT_FOUND_ERROR + raise Dependabot::DependencyFileNotResolvable, "Unable to find hashes for package #{name}" + end + + result.map { |h| "--hash=#{algorithm}:#{h['hash']}" } end def original_dependency_declaration_string(old_req) diff --git a/python/spec/dependabot/python/file_updater/requirement_file_updater_spec.rb b/python/spec/dependabot/python/file_updater/requirement_file_updater_spec.rb index a35abeb723..7d34081389 100644 --- a/python/spec/dependabot/python/file_updater/requirement_file_updater_spec.rb +++ b/python/spec/dependabot/python/file_updater/requirement_file_updater_spec.rb @@ -90,6 +90,36 @@ its(:content) { is_expected.to include "psycopg2==2.8.1 # Comment!\n" } end + context "with hashes" do + let(:dependency) do + Dependabot::Dependency.new( + name: "asml_patch_helper", + version: "18.2.0", + requirements: [{ + file: "requirements.txt", + requirement: updated_requirement_string, + groups: [], + source: nil + }], + previous_requirements: [{ + file: "requirements.txt", + requirement: previous_requirement_string, + groups: [], + source: nil + }], + package_manager: "pip" + ) + end + + let(:requirements_fixture_name) { "asml.txt" } + let(:previous_requirement_string) { "==24.3.3" } + let(:updated_requirement_string) { "==24.4.0" } + its(:content) do + is_expected.to include "asml_patch_helper==24.4.0 \\\n" + is_expected.to include " --hash=sha256:94723f660aa638ac9154c6ffefca0de718d9bfa741eb96a99894300f764960ed\n" + end + end + context "when there is a range" do context "with a space after the comma" do let(:requirements_fixture_name) { "version_between_bounds.txt" } diff --git a/python/spec/dependabot/python/file_updater/requirement_replacer_spec.rb b/python/spec/dependabot/python/file_updater/requirement_replacer_spec.rb index d2928547a2..063664d203 100644 --- a/python/spec/dependabot/python/file_updater/requirement_replacer_spec.rb +++ b/python/spec/dependabot/python/file_updater/requirement_replacer_spec.rb @@ -82,6 +82,20 @@ it { is_expected.to include("Flask-SQLAlchemy\n") } it { is_expected.to include("zope.SQLAlchemy\n") } end + + context "with a hash algorithm" do + let(:old_requirement) { "attrs==18.2.0" } + let(:new_requirement) { "attrs==18.3.0" } + let(:requirement_content) do + <<~REQUIREMENT + attrs==18.2.0 \ + --hash=sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69 \ + --hash=sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb + REQUIREMENT + end + let(:dependency_name) { "attrs" } + it { is_expected.to eq("attrs==1.11.5") } + end end end end diff --git a/python/spec/fixtures/requirements/hashes_unknown_package.txt b/python/spec/fixtures/requirements/hashes_unknown_package.txt new file mode 100644 index 0000000000..55c73e9382 --- /dev/null +++ b/python/spec/fixtures/requirements/hashes_unknown_package.txt @@ -0,0 +1,9 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# pip-compile --generate-hashes --output-file req.txt req.in +# +some_unknown_package==24.3.3 \ + --hash=sha256:9373346h0aa638ac9154c6ffefca0de818d9bfa741eb66a99894300f734560de \ + --hash=sha256:41b2efa462f3d37a138443bbed5ca1affa9322aaa8h35d182be9be98c03f4bc9 From ecc2f728c343cac86176f0b4c17019796ff9f501 Mon Sep 17 00:00:00 2001 From: Rob Aiken Date: Mon, 29 Apr 2024 18:30:38 +0100 Subject: [PATCH 02/11] Pass index url to hasher.py --- python/helpers/lib/hasher.py | 5 ++-- python/lib/dependabot/python/file_updater.rb | 20 ++++++++++++--- .../file_updater/pip_compile_file_updater.rb | 25 ++++++++++++------- .../file_updater/requirement_file_updater.rb | 6 +++-- .../file_updater/requirement_replacer.rb | 23 ++++++++++------- 5 files changed, 54 insertions(+), 25 deletions(-) diff --git a/python/helpers/lib/hasher.py b/python/helpers/lib/hasher.py index b6e01aa29d..000dc136cc 100644 --- a/python/helpers/lib/hasher.py +++ b/python/helpers/lib/hasher.py @@ -5,12 +5,13 @@ from poetry.factory import Factory -def get_dependency_hash(dependency_name, dependency_version, algorithm): +def get_dependency_hash(dependency_name, dependency_version, algorithm, index_url=hashin.DEFAULT_INDEX_URL): try: hashes = hashin.get_package_hashes( dependency_name, version=dependency_version, - algorithm=algorithm + algorithm=algorithm, + index_url=index_url ) return json.dumps({"result": hashes["hashes"]}) except hashin.PackageNotFoundError as e: diff --git a/python/lib/dependabot/python/file_updater.rb b/python/lib/dependabot/python/file_updater.rb index 78cc978ef0..f4d8b7a526 100644 --- a/python/lib/dependabot/python/file_updater.rb +++ b/python/lib/dependabot/python/file_updater.rb @@ -105,18 +105,32 @@ def updated_pip_compile_based_files PipCompileFileUpdater.new( dependencies: dependencies, dependency_files: dependency_files, - credentials: credentials - ).updated_dependency_files + credentials: credentials, + index_urls: pip_compile_index_urls, + + ).updated_dependency_files end def updated_requirement_based_files RequirementFileUpdater.new( dependencies: dependencies, dependency_files: dependency_files, - credentials: credentials + credentials: credentials, + index_urls: pip_compile_index_urls ).updated_dependency_files end + def pip_compile_index_urls + if credentials.any?(&:replaces_base?) + credentials.select(&:replaces_base?).map { |cred| AuthedUrlBuilder.authed_url(credential: cred) } + else + urls = credentials.map { |cred| AuthedUrlBuilder.authed_url(credential: cred) } + # If there are no credentials that replace the base, we need to + # ensure that the base URL is included in the list of extra-index-urls. + [nil, *urls] + end + end + def check_required_files filenames = dependency_files.map(&:name) return if filenames.any? { |name| name.end_with?(".txt", ".in") } diff --git a/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb b/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb index 6e6fbc96d6..48d92bd01c 100644 --- a/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb +++ b/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb @@ -34,10 +34,11 @@ class PipCompileFileUpdater attr_reader :dependency_files attr_reader :credentials - def initialize(dependencies:, dependency_files:, credentials:) + def initialize(dependencies:, dependency_files:, credentials:, index_urls:) @dependencies = dependencies @dependency_files = dependency_files @credentials = credentials + @index_urls = index_urls @build_isolation = true end @@ -261,12 +262,12 @@ def freeze_dependency_requirement(file) return file.content unless old_req return file.content if old_req == "==#{dependency.version}" - debugger RequirementReplacer.new( content: file.content, dependency_name: dependency.name, old_requirement: old_req[:requirement], - new_requirement: "==#{dependency.version}" + new_requirement: "==#{dependency.version}", + index_urls: index_urls ).updated_content end @@ -284,7 +285,8 @@ def update_dependency_requirement(file) content: file.content, dependency_name: dependency.name, old_requirement: old_req[:requirement], - new_requirement: new_req[:requirement] + new_requirement: new_req[:requirement], + index_urls: index_urls ).updated_content end @@ -390,11 +392,16 @@ def deps_to_augment_hashes_for(updated_content, original_content) end def package_hashes_for(name:, version:, algorithm:) - SharedHelpers.run_helper_subprocess( - command: "pyenv exec python3 #{NativeHelpers.python_helper_path}", - function: "get_dependency_hash", - args: [name, version, algorithm] - ).map { |h| "--hash=#{algorithm}:#{h['hash']}" } + args = [name, version, algorithm] + + index_urls.map do |index_url| + args << index_url unless index_url.nil? + SharedHelpers.run_helper_subprocess( + command: "pyenv exec python3 #{NativeHelpers.python_helper_path}", + function: "get_dependency_hash", + args: args, + ).map { |h| "--hash=#{algorithm}:#{h['hash']}" } + end end def hash_separator(requirement_string) diff --git a/python/lib/dependabot/python/file_updater/requirement_file_updater.rb b/python/lib/dependabot/python/file_updater/requirement_file_updater.rb index fa9266b43f..41755a9a3a 100644 --- a/python/lib/dependabot/python/file_updater/requirement_file_updater.rb +++ b/python/lib/dependabot/python/file_updater/requirement_file_updater.rb @@ -16,10 +16,11 @@ class RequirementFileUpdater attr_reader :dependency_files attr_reader :credentials - def initialize(dependencies:, dependency_files:, credentials:) + def initialize(dependencies:, dependency_files:, credentials:, index_urls:nil) @dependencies = dependencies @dependency_files = dependency_files @credentials = credentials + @index_urls = index_urls end def updated_dependency_files @@ -58,7 +59,8 @@ def updated_requirement_or_setup_file_content(new_req, old_req) dependency_name: dependency.name, old_requirement: old_req.fetch(:requirement), new_requirement: new_req.fetch(:requirement), - new_hash_version: dependency.version + new_hash_version: dependency.version, + index_urls: index_urls ).updated_content end diff --git a/python/lib/dependabot/python/file_updater/requirement_replacer.rb b/python/lib/dependabot/python/file_updater/requirement_replacer.rb index 8eebf0e0e9..50254e3bde 100644 --- a/python/lib/dependabot/python/file_updater/requirement_replacer.rb +++ b/python/lib/dependabot/python/file_updater/requirement_replacer.rb @@ -15,12 +15,13 @@ class RequirementReplacer PACKAGE_NOT_FOUND_ERROR = "PackageNotFoundError" def initialize(content:, dependency_name:, old_requirement:, - new_requirement:, new_hash_version: nil) + new_requirement:, new_hash_version: nil, index_urls: nil) @content = content @dependency_name = normalise(dependency_name) @old_requirement = old_requirement @new_requirement = new_requirement @new_hash_version = new_hash_version + @index_urls = index_urls end def updated_content @@ -42,6 +43,7 @@ def updated_content attr_reader :content attr_reader :dependency_name + attr_reader :index_url attr_reader :old_requirement attr_reader :new_requirement attr_reader :new_hash_version @@ -139,17 +141,20 @@ def hash_separator(requirement) end def package_hashes_for(name:, version:, algorithm:) - result = SharedHelpers.run_helper_subprocess( - command: "pyenv exec python3 #{NativeHelpers.python_helper_path}", - function: "get_dependency_hash", - args: [name, version, algorithm] - ) + index_urls.map do |index_url| + args << index_url unless index_url.nil? + result = SharedHelpers.run_helper_subprocess( + command: "pyenv exec python3 #{NativeHelpers.python_helper_path}", + function: "get_dependency_hash", + args: [name, version, algorithm] + ) + + result.map { |h| "--hash=#{algorithm}:#{h['hash']}" } if result.id_a?(Array) - if result["error_class"] == PACKAGE_NOT_FOUND_ERROR - raise Dependabot::DependencyFileNotResolvable, "Unable to find hashes for package #{name}" + next if result["error_class"] == PACKAGE_NOT_FOUND_ERROR end - result.map { |h| "--hash=#{algorithm}:#{h['hash']}" } + raise Dependabot::DependencyFileNotResolvable, "Unable to find hashes for package #{name}" end def original_dependency_declaration_string(old_req) From 957a25002b7c50c668a4f7b6211152e6aa6d6409 Mon Sep 17 00:00:00 2001 From: Rob Aiken Date: Mon, 29 Apr 2024 18:37:13 +0100 Subject: [PATCH 03/11] lint --- python/lib/dependabot/python/file_updater.rb | 5 ++--- .../python/file_updater/pip_compile_file_updater.rb | 4 ++-- .../python/file_updater/requirement_file_updater.rb | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/python/lib/dependabot/python/file_updater.rb b/python/lib/dependabot/python/file_updater.rb index f4d8b7a526..c7d4a7c88b 100644 --- a/python/lib/dependabot/python/file_updater.rb +++ b/python/lib/dependabot/python/file_updater.rb @@ -106,9 +106,8 @@ def updated_pip_compile_based_files dependencies: dependencies, dependency_files: dependency_files, credentials: credentials, - index_urls: pip_compile_index_urls, - - ).updated_dependency_files + index_urls: pip_compile_index_urls + ).updated_dependency_files end def updated_requirement_based_files diff --git a/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb b/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb index 48d92bd01c..e62bb99df1 100644 --- a/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb +++ b/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb @@ -399,8 +399,8 @@ def package_hashes_for(name:, version:, algorithm:) SharedHelpers.run_helper_subprocess( command: "pyenv exec python3 #{NativeHelpers.python_helper_path}", function: "get_dependency_hash", - args: args, - ).map { |h| "--hash=#{algorithm}:#{h['hash']}" } + args: args + ).map { |h| "--hash=#{algorithm}:#{h['hash']}" } end end diff --git a/python/lib/dependabot/python/file_updater/requirement_file_updater.rb b/python/lib/dependabot/python/file_updater/requirement_file_updater.rb index 41755a9a3a..1e56c1ed8c 100644 --- a/python/lib/dependabot/python/file_updater/requirement_file_updater.rb +++ b/python/lib/dependabot/python/file_updater/requirement_file_updater.rb @@ -16,7 +16,7 @@ class RequirementFileUpdater attr_reader :dependency_files attr_reader :credentials - def initialize(dependencies:, dependency_files:, credentials:, index_urls:nil) + def initialize(dependencies:, dependency_files:, credentials:, index_urls: nil) @dependencies = dependencies @dependency_files = dependency_files @credentials = credentials From 4052a33da8ddce5f77906ca9e640b92192ccc70e Mon Sep 17 00:00:00 2001 From: Rob Aiken Date: Wed, 1 May 2024 16:02:19 +0100 Subject: [PATCH 04/11] Get hashes from private Python registries, not just PyPI --- .editorconfig | 4 + npm_and_yarn/helpers/package-lock.json | 2 +- python/helpers/lib/hasher.py | 25 +++--- .../file_updater/pip_compile_file_updater.rb | 37 ++++++--- .../file_updater/requirement_file_updater.rb | 2 +- .../file_updater/requirement_replacer.rb | 24 ++++-- .../pyproject_files_parser_spec.rb | 2 +- .../pip_compile_file_updater_spec.rb | 82 +++++++++++++++++++ .../requirement_file_updater_spec.rb | 28 +++++-- .../file_updater/requirement_replacer_spec.rb | 14 ---- .../dependabot/python/file_updater_spec.rb | 32 ++++++++ .../spec/fixtures/pip_compile_files/hashes.in | 3 + 12 files changed, 201 insertions(+), 54 deletions(-) create mode 100644 python/spec/fixtures/pip_compile_files/hashes.in diff --git a/.editorconfig b/.editorconfig index 945dc96d96..e98dd93a50 100644 --- a/.editorconfig +++ b/.editorconfig @@ -15,3 +15,7 @@ indent_style = tab [*.php] indent_size = 4 + +[*.py] +indent_size = 4 +max_line_length = 80 diff --git a/npm_and_yarn/helpers/package-lock.json b/npm_and_yarn/helpers/package-lock.json index e88ecd6659..af1580f8f8 100644 --- a/npm_and_yarn/helpers/package-lock.json +++ b/npm_and_yarn/helpers/package-lock.json @@ -5549,7 +5549,7 @@ "import-local-fixture": "fixtures/cli.js" }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" diff --git a/python/helpers/lib/hasher.py b/python/helpers/lib/hasher.py index 000dc136cc..5ab29d71ac 100644 --- a/python/helpers/lib/hasher.py +++ b/python/helpers/lib/hasher.py @@ -5,19 +5,22 @@ from poetry.factory import Factory -def get_dependency_hash(dependency_name, dependency_version, algorithm, index_url=hashin.DEFAULT_INDEX_URL): +def get_dependency_hash(dependency_name, dependency_version, algorithm, + index_url=hashin.DEFAULT_INDEX_URL): try: - hashes = hashin.get_package_hashes( - dependency_name, - version=dependency_version, - algorithm=algorithm, - index_url=index_url - ) - return json.dumps({"result": hashes["hashes"]}) + hashes = hashin.get_package_hashes( + dependency_name, + version=dependency_version, + algorithm=algorithm, + index_url=index_url + ) + return json.dumps({"result": hashes["hashes"]}) except hashin.PackageNotFoundError as e: - return json.dumps({"error": repr(e), "error_class:" : - e.__class__.__name__, "trace:": - ''.join(traceback.format_stack())}) + return json.dumps({ + "error": repr(e), + "error_class:": e.__class__.__name__, + "trace:": ''.join(traceback.format_stack()) + }) def get_pipfile_hash(directory): diff --git a/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb b/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb index e62bb99df1..8a0bce46e6 100644 --- a/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb +++ b/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb @@ -34,7 +34,7 @@ class PipCompileFileUpdater attr_reader :dependency_files attr_reader :credentials - def initialize(dependencies:, dependency_files:, credentials:, index_urls:) + def initialize(dependencies:, dependency_files:, credentials:, index_urls: nil) @dependencies = dependencies @dependency_files = dependency_files @credentials = credentials @@ -267,7 +267,7 @@ def freeze_dependency_requirement(file) dependency_name: dependency.name, old_requirement: old_req[:requirement], new_requirement: "==#{dependency.version}", - index_urls: index_urls + index_urls: @index_urls ).updated_content end @@ -286,7 +286,7 @@ def update_dependency_requirement(file) dependency_name: dependency.name, old_requirement: old_req[:requirement], new_requirement: new_req[:requirement], - index_urls: index_urls + index_urls: @index_urls ).updated_content end @@ -392,16 +392,29 @@ def deps_to_augment_hashes_for(updated_content, original_content) end def package_hashes_for(name:, version:, algorithm:) - args = [name, version, algorithm] - - index_urls.map do |index_url| - args << index_url unless index_url.nil? - SharedHelpers.run_helper_subprocess( - command: "pyenv exec python3 #{NativeHelpers.python_helper_path}", - function: "get_dependency_hash", - args: args - ).map { |h| "--hash=#{algorithm}:#{h['hash']}" } + index_urls = @index_urls || [nil] + hashes = [] + + index_urls.each do |index_url| + args = [name, version, algorithm] + args << index_url if index_url + + begin + native_helper_hashes = SharedHelpers.run_helper_subprocess( + command: "pyenv exec python3 #{NativeHelpers.python_helper_path}", + function: "get_dependency_hash", + args: args + ).map { |h| "--hash=#{algorithm}:#{h['hash']}" } + + hashes.concat(native_helper_hashes) + rescue SharedHelpers::HelperSubprocessFailed => e + raise unless e.message.include?("PackageNotFoundError") + + next + end end + + hashes end def hash_separator(requirement_string) diff --git a/python/lib/dependabot/python/file_updater/requirement_file_updater.rb b/python/lib/dependabot/python/file_updater/requirement_file_updater.rb index 1e56c1ed8c..54be3eb9be 100644 --- a/python/lib/dependabot/python/file_updater/requirement_file_updater.rb +++ b/python/lib/dependabot/python/file_updater/requirement_file_updater.rb @@ -60,7 +60,7 @@ def updated_requirement_or_setup_file_content(new_req, old_req) old_requirement: old_req.fetch(:requirement), new_requirement: new_req.fetch(:requirement), new_hash_version: dependency.version, - index_urls: index_urls + index_urls: @index_urls ).updated_content end diff --git a/python/lib/dependabot/python/file_updater/requirement_replacer.rb b/python/lib/dependabot/python/file_updater/requirement_replacer.rb index 50254e3bde..1b32b529ea 100644 --- a/python/lib/dependabot/python/file_updater/requirement_replacer.rb +++ b/python/lib/dependabot/python/file_updater/requirement_replacer.rb @@ -141,17 +141,27 @@ def hash_separator(requirement) end def package_hashes_for(name:, version:, algorithm:) + index_urls = @index_urls || [nil] + index_urls.map do |index_url| + args = [name, version, algorithm] args << index_url unless index_url.nil? - result = SharedHelpers.run_helper_subprocess( - command: "pyenv exec python3 #{NativeHelpers.python_helper_path}", - function: "get_dependency_hash", - args: [name, version, algorithm] - ) - result.map { |h| "--hash=#{algorithm}:#{h['hash']}" } if result.id_a?(Array) + begin + result = SharedHelpers.run_helper_subprocess( + command: "pyenv exec python3 #{NativeHelpers.python_helper_path}", + function: "get_dependency_hash", + args: args + ) + rescue SharedHelpers::HelperSubprocessFailed => e + raise unless e.message.include?("PackageNotFoundError") + + next + end + + next if result.first.key?("error_class") && result.first["error_class"] == PACKAGE_NOT_FOUND_ERROR - next if result["error_class"] == PACKAGE_NOT_FOUND_ERROR + return result.map { |h| "--hash=#{algorithm}:#{h['hash']}" } if result.is_a?(Array) end raise Dependabot::DependencyFileNotResolvable, "Unable to find hashes for package #{name}" diff --git a/python/spec/dependabot/python/file_parser/pyproject_files_parser_spec.rb b/python/spec/dependabot/python/file_parser/pyproject_files_parser_spec.rb index b8d96b4d0b..a4186e67a7 100644 --- a/python/spec/dependabot/python/file_parser/pyproject_files_parser_spec.rb +++ b/python/spec/dependabot/python/file_parser/pyproject_files_parser_spec.rb @@ -363,7 +363,7 @@ subject(:dependencies) { parser.dependency_set.dependencies } - its(:length) { is_expected.to be > 0 } + its(:length) { is_expected.to be.positive? } end end end diff --git a/python/spec/dependabot/python/file_updater/pip_compile_file_updater_spec.rb b/python/spec/dependabot/python/file_updater/pip_compile_file_updater_spec.rb index 0daf13587f..f9bf670a7b 100644 --- a/python/spec/dependabot/python/file_updater/pip_compile_file_updater_spec.rb +++ b/python/spec/dependabot/python/file_updater/pip_compile_file_updater_spec.rb @@ -551,4 +551,86 @@ end end end + + describe "#package_hashes_for" do + let(:name) { "package_name" } + let(:version) { "1.0.0" } + let(:algorithm) { "sha256" } + + context "when index_urls is not set" do + let(:updater) do + described_class.new( + dependencies: [], + dependency_files: [], + credentials: [] + ) + end + + before do + allow(Dependabot::SharedHelpers).to receive(:run_helper_subprocess).and_return([{ "hash" => "123abc" }]) + end + + it "returns hash" do + result = updater.send(:package_hashes_for, name: name, version: version, algorithm: algorithm) + expect(result).to eq(["--hash=sha256:123abc"]) + end + end + + context "when multiple index_urls are set" do + let(:updater) do + described_class.new( + dependencies: [], + dependency_files: [], + credentials: [], + index_urls: [nil, "http://example.com"] + ) + end + + before do + allow(Dependabot::SharedHelpers).to receive(:run_helper_subprocess) + .and_return([{ "hash" => "123abc" }], [{ "hash" => "312cba" }]) + end + + it "returns returns two hashes" do + result = updater.send(:package_hashes_for, name: name, version: version, algorithm: algorithm) + expect(result).to eq(%w(--hash=sha256:123abc --hash=sha256:312cba)) + end + end + + context "when multiple index_urls are set but package does not exist in PyPI" do + let(:updater) do + described_class.new( + dependencies: [], + dependency_files: [], + credentials: [], + index_urls: [nil, "http://example.com"] + ) + end + + before do + allow(Dependabot::SharedHelpers).to receive(:run_helper_subprocess).with({ + args: %w(package_name 1.0.0 + sha256), + command: "pyenv exec python3 /opt/python/run.py", + function: "get_dependency_hash" + }).and_raise( + Dependabot::SharedHelpers::HelperSubprocessFailed.new( + message: "Error message", error_context: {} + ) + ) + + allow(Dependabot::SharedHelpers).to receive(:run_helper_subprocess) + .with({ + args: %w(package_name 1.0.0 sha256 http://example.com), + command: "pyenv exec python3 /opt/python/run.py", + function: "get_dependency_hash" + }).and_return([{ "hash" => "123abc" }]) + end + + it "returns returns two hashes" do + result = updater.send(:package_hashes_for, name: name, version: version, algorithm: algorithm) + expect(result).to eq(["--hash=sha256:123abc"]) + end + end + end end diff --git a/python/spec/dependabot/python/file_updater/requirement_file_updater_spec.rb b/python/spec/dependabot/python/file_updater/requirement_file_updater_spec.rb index 7d34081389..6743a26080 100644 --- a/python/spec/dependabot/python/file_updater/requirement_file_updater_spec.rb +++ b/python/spec/dependabot/python/file_updater/requirement_file_updater_spec.rb @@ -90,11 +90,11 @@ its(:content) { is_expected.to include "psycopg2==2.8.1 # Comment!\n" } end - context "with hashes" do + context "with an unknown package" do let(:dependency) do Dependabot::Dependency.new( - name: "asml_patch_helper", - version: "18.2.0", + name: "some_unknown_package", + version: "24.3.3", requirements: [{ file: "requirements.txt", requirement: updated_requirement_string, @@ -111,12 +111,26 @@ ) end - let(:requirements_fixture_name) { "asml.txt" } + let(:requirements_fixture_name) { "hashes_unknown_package.txt" } let(:previous_requirement_string) { "==24.3.3" } let(:updated_requirement_string) { "==24.4.0" } - its(:content) do - is_expected.to include "asml_patch_helper==24.4.0 \\\n" - is_expected.to include " --hash=sha256:94723f660aa638ac9154c6ffefca0de718d9bfa741eb96a99894300f764960ed\n" + + context "when package is not in default index" do + it "raises an error" do + expect { updated_files }.to raise_error(Dependabot::DependencyFileNotResolvable) + end + end + + context "when package is in default index" do + before do + allow(Dependabot::SharedHelpers).to receive(:run_helper_subprocess) + .and_return([{ "hash" => "1234567890abcdef" }]) + end + + its(:content) do + is_expected.to include "some_unknown_package==24.4.0" + is_expected.to include "--hash=sha256:1234567890abcdef" + end end end diff --git a/python/spec/dependabot/python/file_updater/requirement_replacer_spec.rb b/python/spec/dependabot/python/file_updater/requirement_replacer_spec.rb index 063664d203..d2928547a2 100644 --- a/python/spec/dependabot/python/file_updater/requirement_replacer_spec.rb +++ b/python/spec/dependabot/python/file_updater/requirement_replacer_spec.rb @@ -82,20 +82,6 @@ it { is_expected.to include("Flask-SQLAlchemy\n") } it { is_expected.to include("zope.SQLAlchemy\n") } end - - context "with a hash algorithm" do - let(:old_requirement) { "attrs==18.2.0" } - let(:new_requirement) { "attrs==18.3.0" } - let(:requirement_content) do - <<~REQUIREMENT - attrs==18.2.0 \ - --hash=sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69 \ - --hash=sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb - REQUIREMENT - end - let(:dependency_name) { "attrs" } - it { is_expected.to eq("attrs==1.11.5") } - end end end end diff --git a/python/spec/dependabot/python/file_updater_spec.rb b/python/spec/dependabot/python/file_updater_spec.rb index 755029eaf3..b57148235a 100644 --- a/python/spec/dependabot/python/file_updater_spec.rb +++ b/python/spec/dependabot/python/file_updater_spec.rb @@ -391,5 +391,37 @@ updated_files.each { |f| expect(f).to be_a(Dependabot::DependencyFile) } end end + + describe "#pip_compile_index_urls" do + let(:instance) do + described_class.new( + dependencies: [], + dependency_files: [], + credentials: credentials + ) + end + + let(:credentials) { [double(replaces_base?: replaces_base)] } + let(:replaces_base) { false } + + before do + allow_any_instance_of(Dependabot::Python::FileUpdater).to receive(:check_required_files).and_return(true) + allow(Dependabot::Python::AuthedUrlBuilder).to receive(:authed_url).and_return("authed_url") + end + + context "when credentials replace base" do + let(:replaces_base) { true } + + it "returns authed urls for these credentials" do + expect(instance.send(:pip_compile_index_urls)).to eq(["authed_url"]) + end + end + + context "when credentials do not replace base" do + it "returns nil and authed urls for all credentials" do + expect(instance.send(:pip_compile_index_urls)).to eq([nil, "authed_url"]) + end + end + end end end diff --git a/python/spec/fixtures/pip_compile_files/hashes.in b/python/spec/fixtures/pip_compile_files/hashes.in new file mode 100644 index 0000000000..eb85b78b04 --- /dev/null +++ b/python/spec/fixtures/pip_compile_files/hashes.in @@ -0,0 +1,3 @@ +attrs==18.2.0 + --hash=sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69 + --hash=sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb From 435c4c82a7a6347bb9a0a6ca0c4da9b49595055e Mon Sep 17 00:00:00 2001 From: Rob Aiken Date: Wed, 1 May 2024 16:04:36 +0100 Subject: [PATCH 05/11] revert file --- npm_and_yarn/helpers/package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm_and_yarn/helpers/package-lock.json b/npm_and_yarn/helpers/package-lock.json index af1580f8f8..e88ecd6659 100644 --- a/npm_and_yarn/helpers/package-lock.json +++ b/npm_and_yarn/helpers/package-lock.json @@ -5549,7 +5549,7 @@ "import-local-fixture": "fixtures/cli.js" }, "engines": { - "node": ">=18" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" From d4b94b7a7ae7c97914a3e3f8d07ffafa8b24673f Mon Sep 17 00:00:00 2001 From: Rob Aiken Date: Wed, 1 May 2024 16:19:43 +0100 Subject: [PATCH 06/11] removed redundant line --- .../dependabot/python/file_updater/pip_compile_file_updater.rb | 1 - .../lib/dependabot/python/file_updater/requirement_replacer.rb | 3 --- 2 files changed, 4 deletions(-) diff --git a/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb b/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb index 8a0bce46e6..b563070b9a 100644 --- a/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb +++ b/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb @@ -409,7 +409,6 @@ def package_hashes_for(name:, version:, algorithm:) hashes.concat(native_helper_hashes) rescue SharedHelpers::HelperSubprocessFailed => e raise unless e.message.include?("PackageNotFoundError") - next end end diff --git a/python/lib/dependabot/python/file_updater/requirement_replacer.rb b/python/lib/dependabot/python/file_updater/requirement_replacer.rb index 1b32b529ea..a3d4b66b11 100644 --- a/python/lib/dependabot/python/file_updater/requirement_replacer.rb +++ b/python/lib/dependabot/python/file_updater/requirement_replacer.rb @@ -155,12 +155,9 @@ def package_hashes_for(name:, version:, algorithm:) ) rescue SharedHelpers::HelperSubprocessFailed => e raise unless e.message.include?("PackageNotFoundError") - next end - next if result.first.key?("error_class") && result.first["error_class"] == PACKAGE_NOT_FOUND_ERROR - return result.map { |h| "--hash=#{algorithm}:#{h['hash']}" } if result.is_a?(Array) end From fc6597e159b8ead25f0c3e02fee43493f8560064 Mon Sep 17 00:00:00 2001 From: Rob Aiken Date: Wed, 1 May 2024 16:22:36 +0100 Subject: [PATCH 07/11] lint --- .../dependabot/python/file_updater/pip_compile_file_updater.rb | 1 + .../lib/dependabot/python/file_updater/requirement_replacer.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb b/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb index b563070b9a..8a0bce46e6 100644 --- a/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb +++ b/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb @@ -409,6 +409,7 @@ def package_hashes_for(name:, version:, algorithm:) hashes.concat(native_helper_hashes) rescue SharedHelpers::HelperSubprocessFailed => e raise unless e.message.include?("PackageNotFoundError") + next end end diff --git a/python/lib/dependabot/python/file_updater/requirement_replacer.rb b/python/lib/dependabot/python/file_updater/requirement_replacer.rb index a3d4b66b11..5c595fec19 100644 --- a/python/lib/dependabot/python/file_updater/requirement_replacer.rb +++ b/python/lib/dependabot/python/file_updater/requirement_replacer.rb @@ -155,6 +155,7 @@ def package_hashes_for(name:, version:, algorithm:) ) rescue SharedHelpers::HelperSubprocessFailed => e raise unless e.message.include?("PackageNotFoundError") + next end From f40e8853676dcb3f924c696cf351c117f116bf47 Mon Sep 17 00:00:00 2001 From: Rob Aiken Date: Wed, 1 May 2024 16:27:49 +0100 Subject: [PATCH 08/11] removing old fixture file --- python/spec/fixtures/pip_compile_files/hashes.in | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 python/spec/fixtures/pip_compile_files/hashes.in diff --git a/python/spec/fixtures/pip_compile_files/hashes.in b/python/spec/fixtures/pip_compile_files/hashes.in deleted file mode 100644 index eb85b78b04..0000000000 --- a/python/spec/fixtures/pip_compile_files/hashes.in +++ /dev/null @@ -1,3 +0,0 @@ -attrs==18.2.0 - --hash=sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69 - --hash=sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb From 367279923615f63877f8d9003679bd595f7f0d1d Mon Sep 17 00:00:00 2001 From: Rob Aiken Date: Wed, 1 May 2024 16:50:18 +0100 Subject: [PATCH 09/11] updated test to reflect actual error --- .../python/file_updater/pip_compile_file_updater.rb | 2 +- .../python/file_updater/pip_compile_file_updater_spec.rb | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb b/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb index 8a0bce46e6..d635238129 100644 --- a/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb +++ b/python/lib/dependabot/python/file_updater/pip_compile_file_updater.rb @@ -408,7 +408,7 @@ def package_hashes_for(name:, version:, algorithm:) hashes.concat(native_helper_hashes) rescue SharedHelpers::HelperSubprocessFailed => e - raise unless e.message.include?("PackageNotFoundError") + raise unless e.error_class.include?("PackageNotFoundError") next end diff --git a/python/spec/dependabot/python/file_updater/pip_compile_file_updater_spec.rb b/python/spec/dependabot/python/file_updater/pip_compile_file_updater_spec.rb index f9bf670a7b..02d261292c 100644 --- a/python/spec/dependabot/python/file_updater/pip_compile_file_updater_spec.rb +++ b/python/spec/dependabot/python/file_updater/pip_compile_file_updater_spec.rb @@ -609,13 +609,12 @@ before do allow(Dependabot::SharedHelpers).to receive(:run_helper_subprocess).with({ - args: %w(package_name 1.0.0 - sha256), + args: %w(package_name 1.0.0 sha256), command: "pyenv exec python3 /opt/python/run.py", function: "get_dependency_hash" }).and_raise( Dependabot::SharedHelpers::HelperSubprocessFailed.new( - message: "Error message", error_context: {} + message: "Error message", error_context: {}, error_class: "PackageNotFoundError" ) ) From ace57b9744b32597fb2c0224dc13a6e5af1f122c Mon Sep 17 00:00:00 2001 From: Rob Aiken Date: Wed, 1 May 2024 17:03:36 +0100 Subject: [PATCH 10/11] fixing issue from rubocop auto fix --- .../python/file_parser/pyproject_files_parser_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/spec/dependabot/python/file_parser/pyproject_files_parser_spec.rb b/python/spec/dependabot/python/file_parser/pyproject_files_parser_spec.rb index a4186e67a7..b8d96b4d0b 100644 --- a/python/spec/dependabot/python/file_parser/pyproject_files_parser_spec.rb +++ b/python/spec/dependabot/python/file_parser/pyproject_files_parser_spec.rb @@ -363,7 +363,7 @@ subject(:dependencies) { parser.dependency_set.dependencies } - its(:length) { is_expected.to be.positive? } + its(:length) { is_expected.to be > 0 } end end end From 4eb2d9ea362b9b8f59dcaa67efeda0e96a6d1ab4 Mon Sep 17 00:00:00 2001 From: Rob Aiken Date: Thu, 2 May 2024 10:10:40 +0100 Subject: [PATCH 11/11] remove attr_reader --- .../lib/dependabot/python/file_updater/requirement_replacer.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/python/lib/dependabot/python/file_updater/requirement_replacer.rb b/python/lib/dependabot/python/file_updater/requirement_replacer.rb index 5c595fec19..b57049cb46 100644 --- a/python/lib/dependabot/python/file_updater/requirement_replacer.rb +++ b/python/lib/dependabot/python/file_updater/requirement_replacer.rb @@ -43,7 +43,6 @@ def updated_content attr_reader :content attr_reader :dependency_name - attr_reader :index_url attr_reader :old_requirement attr_reader :new_requirement attr_reader :new_hash_version