diff --git a/silent/lib/dependabot/silent/file_parser.rb b/silent/lib/dependabot/silent/file_parser.rb index a88a17c8891..a7c4f7203dc 100644 --- a/silent/lib/dependabot/silent/file_parser.rb +++ b/silent/lib/dependabot/silent/file_parser.rb @@ -4,6 +4,8 @@ require "dependabot/dependency" require "dependabot/file_parsers" require "dependabot/file_parsers/base" +require "dependabot/package_manager" +require "dependabot/silent/package_manager" require "sorbet-runtime" module SilentPackageManager @@ -26,6 +28,19 @@ def parse raise Dependabot::DependencyFileNotParseable, T.must(dependency_files.first).path end + sig { returns(Dependabot::PackageManagerBase) } + def package_manager + meta_data = JSON.parse(manifest_content)["silent"] + silent_version = if meta_data.nil? + "2" + else + meta_data["version"] + end + Dependabot::Silent::PackageManager.new(silent_version) + rescue JSON::ParserError + raise Dependabot::DependencyFileNotParseable, T.must(dependency_files.first).path + end + private sig { params(name: String, info: String).returns(Dependabot::Dependency) } diff --git a/silent/lib/dependabot/silent/package_manager.rb b/silent/lib/dependabot/silent/package_manager.rb new file mode 100644 index 00000000000..7f8d54f10f3 --- /dev/null +++ b/silent/lib/dependabot/silent/package_manager.rb @@ -0,0 +1,45 @@ +# typed: strong +# frozen_string_literal: true + +require "sorbet-runtime" +require "dependabot/silent/version" +require "dependabot/package_manager" + +module Dependabot + module Silent + PACKAGE_MANAGER = "silent" + + SUPPORTED_SILENT_VERSIONS = T.let([Version.new("2")].freeze, T::Array[Dependabot::Version]) + DEPRECATED_SILENT_VERSIONS = T.let([Version.new("1")].freeze, T::Array[Dependabot::Version]) + + class PackageManager < PackageManagerBase + extend T::Sig + + sig { params(version: T.any(String, Dependabot::Version)).void } + def initialize(version) + @version = T.let(Version.new(version), Dependabot::Version) + @name = T.let(PACKAGE_MANAGER, String) + @deprecated_versions = T.let(DEPRECATED_SILENT_VERSIONS, T::Array[Dependabot::Version]) + @supported_versions = T.let(SUPPORTED_SILENT_VERSIONS, T::Array[Dependabot::Version]) + end + + sig { override.returns(String) } + attr_reader :name + + sig { override.returns(Dependabot::Version) } + attr_reader :version + + sig { override.returns(T::Array[Dependabot::Version]) } + attr_reader :deprecated_versions + + sig { override.returns(T::Array[Dependabot::Version]) } + attr_reader :supported_versions + + sig { override.returns(T::Boolean) } + def unsupported? + # Check if the version is not supported + supported_versions.all? { |supported| supported > version } + end + end + end +end diff --git a/silent/tests/testdata/vu-unsupported.txt b/silent/tests/testdata/vu-unsupported.txt new file mode 100644 index 00000000000..179c662950b --- /dev/null +++ b/silent/tests/testdata/vu-unsupported.txt @@ -0,0 +1,22 @@ +! dependabot update -f input.yml --local . --updater-image ghcr.io/dependabot/dependabot-updater-silent +! stderr 'created \| dependency-a \( from 1.2.3 to 1.2.5 \)' +! pr-created expected.json +stderr 'Currently, the following silent versions are supported in Dependabot: v2\.*\.' +stdout {"data":{"error-type":"tool_version_not_supported","error-details":{"detected-version":"1","supported-versions":"v2.*","tool-name":"silent"}},"type":"record_update_job_error"} + + +-- manifest.json -- +{ + "silent": { "version": "1" }, + "dependency-a": { "version": "1.2.3" } +} + +-- input.yml -- +job: + package-manager: "silent" + source: + directory: "/" + provider: example + hostname: example.com + api-endpoint: https://example.com/api/v3 + repo: dependabot/smoke-tests diff --git a/updater/lib/dependabot/dependency_snapshot.rb b/updater/lib/dependabot/dependency_snapshot.rb index b9c2c8530b0..02b03c4ef46 100644 --- a/updater/lib/dependabot/dependency_snapshot.rb +++ b/updater/lib/dependabot/dependency_snapshot.rb @@ -241,8 +241,10 @@ def dependency_file_parser reject_external_code: job.reject_external_code?, options: job.experiments ) - # Add 'package_manager' to the depedency_snapshopt to use it in operations' + # Add 'package_manager' to the dependency_snapshot to use it in operations package_manager = parser.package_manager + # Raise an error if the package manager version is unsupported + package_manager&.raise_if_unsupported! @package_manager[@current_directory] = package_manager diff --git a/updater/lib/dependabot/update_files_command.rb b/updater/lib/dependabot/update_files_command.rb index b3cb91d74b9..d04b4abc8d8 100644 --- a/updater/lib/dependabot/update_files_command.rb +++ b/updater/lib/dependabot/update_files_command.rb @@ -67,7 +67,7 @@ def base_commit_sha Environment.job_definition["base_commit_sha"] end - # rubocop:disable Metrics/AbcSize, Layout/LineLength + # rubocop:disable Metrics/AbcSize, Layout/LineLength, Metrics/MethodLength def handle_parser_error(error) # This happens if the repo gets removed after a job gets kicked off. # The service will handle the removal without any prompt from the updater, @@ -80,6 +80,16 @@ def handle_parser_error(error) # Check if the error is a known "run halting" state we should handle if (error_type = Updater::ErrorHandler::RUN_HALTING_ERRORS[error.class]) { "error-type": error_type } + elsif error.is_a?(ToolVersionNotSupported) + Dependabot.logger.error(error.message) + { + "error-type": "tool_version_not_supported", + "error-detail": { + "tool-name": error.tool_name, + "detected-version": error.detected_version, + "supported-versions": error.supported_versions + } + } else # If it isn't, then log all the details and let the application error # tracker know about it @@ -118,6 +128,6 @@ def handle_parser_error(error) error_details: error_details[:"error-detail"] ) end - # rubocop:enable Metrics/AbcSize, Layout/LineLength + # rubocop:enable Metrics/AbcSize, Layout/LineLength, Metrics/MethodLength end end diff --git a/updater/lib/dependabot/updater/group_update_creation.rb b/updater/lib/dependabot/updater/group_update_creation.rb index 9494698c4cc..abf23f6492f 100644 --- a/updater/lib/dependabot/updater/group_update_creation.rb +++ b/updater/lib/dependabot/updater/group_update_creation.rb @@ -241,9 +241,6 @@ def compile_updates_for(dependency, dependency_files, group) # rubocop:disable M return [] end - # Raise an error if the package manager version is unsupported - dependency_snapshot.package_manager&.raise_if_unsupported! - checker.updated_dependencies( requirements_to_unlock: requirements_to_unlock ) diff --git a/updater/lib/dependabot/updater/operations/create_security_update_pull_request.rb b/updater/lib/dependabot/updater/operations/create_security_update_pull_request.rb index d10754aa7c2..156a48f3b36 100644 --- a/updater/lib/dependabot/updater/operations/create_security_update_pull_request.rb +++ b/updater/lib/dependabot/updater/operations/create_security_update_pull_request.rb @@ -105,7 +105,6 @@ def check_and_create_pr_with_error_handling(dependency) # rubocop:disable Metrics/AbcSize # rubocop:disable Metrics/PerceivedComplexity # rubocop:disable Metrics/MethodLength - # rubocop:disable Metrics/CyclomaticComplexity sig { params(dependency: Dependabot::Dependency).void } def check_and_create_pull_request(dependency) dependency = vulnerable_version(dependency) if dependency.metadata[:all_versions] @@ -146,9 +145,6 @@ def check_and_create_pull_request(dependency) log_requirements_for_update(requirements_to_unlock, checker) return record_security_update_not_possible_error(checker) if requirements_to_unlock == :update_not_possible - # Raise an error if the package manager version is unsupported - dependency_snapshot.package_manager&.raise_if_unsupported! - updated_deps = checker.updated_dependencies( requirements_to_unlock: requirements_to_unlock ) @@ -202,7 +198,6 @@ def check_and_create_pull_request(dependency) # rubocop:enable Metrics/MethodLength # rubocop:enable Metrics/AbcSize # rubocop:enable Metrics/PerceivedComplexity - # rubocop:enable Metrics/CyclomaticComplexity sig { params(dependency: Dependabot::Dependency).returns(Dependabot::Dependency) } def vulnerable_version(dependency) return dependency if dependency.metadata[:all_versions].count == 1 diff --git a/updater/lib/dependabot/updater/operations/refresh_security_update_pull_request.rb b/updater/lib/dependabot/updater/operations/refresh_security_update_pull_request.rb index 6202dc7a358..a70b466217c 100644 --- a/updater/lib/dependabot/updater/operations/refresh_security_update_pull_request.rb +++ b/updater/lib/dependabot/updater/operations/refresh_security_update_pull_request.rb @@ -54,9 +54,6 @@ def perform Dependabot.logger.info("Starting update job for #{job.source.repo}") Dependabot.logger.info("Checking and updating security pull requests...") - # Raise an error if the package manager version is unsupported - dependency_snapshot.package_manager&.raise_if_unsupported! - # Retrieve the list of initial notices from dependency snapshot @notices = dependency_snapshot.notices # More notices can be added during the update process diff --git a/updater/lib/dependabot/updater/operations/refresh_version_update_pull_request.rb b/updater/lib/dependabot/updater/operations/refresh_version_update_pull_request.rb index 4919c758986..aafa2b2e13f 100644 --- a/updater/lib/dependabot/updater/operations/refresh_version_update_pull_request.rb +++ b/updater/lib/dependabot/updater/operations/refresh_version_update_pull_request.rb @@ -138,9 +138,6 @@ def check_and_update_pull_request(dependencies) return close_pull_request(reason: :update_no_longer_possible) end - # Raise an error if the package manager version is unsupported - dependency_snapshot.package_manager&.raise_if_unsupported! - updated_deps = checker.updated_dependencies( requirements_to_unlock: requirements_to_unlock ) diff --git a/updater/lib/dependabot/updater/operations/update_all_versions.rb b/updater/lib/dependabot/updater/operations/update_all_versions.rb index 40e63264e62..bdcc9a20c4b 100644 --- a/updater/lib/dependabot/updater/operations/update_all_versions.rb +++ b/updater/lib/dependabot/updater/operations/update_all_versions.rb @@ -172,9 +172,6 @@ def check_and_create_pull_request(dependency) notices: @notices ) - # Raise an error if the package manager version is unsupported - dependency_snapshot.package_manager&.raise_if_unsupported! - if dependency_change.updated_dependency_files.empty? raise "UpdateChecker found viable dependencies to be updated, but FileUpdater failed to update any files" end diff --git a/updater/spec/dependabot/dependency_snapshot_spec.rb b/updater/spec/dependabot/dependency_snapshot_spec.rb index 2830dfe8fc4..fc63e23a94c 100644 --- a/updater/spec/dependabot/dependency_snapshot_spec.rb +++ b/updater/spec/dependabot/dependency_snapshot_spec.rb @@ -68,6 +68,21 @@ ] end + let(:dependency_files_for_unsupported) do + [ + Dependabot::DependencyFile.new( + name: "Gemfile", + content: fixture("bundler/unsupported/Gemfile"), + directory: directory + ), + Dependabot::DependencyFile.new( + name: "Gemfile.lock", + content: fixture("bundler/unsupported/Gemfile.lock"), + directory: directory + ) + ] + end + let(:dependency_groups) do [ { @@ -84,6 +99,21 @@ "mock-sha" end + let(:unsupported_error_enabled) { false } + + before do + allow(Dependabot::Experiments).to receive(:enabled?) + .with(:bundler_v1_unsupported_error) + .and_return(unsupported_error_enabled) + allow(Dependabot::Experiments).to receive(:enabled?) + .with(:add_deprecation_warn_to_pr_message) + .and_return(true) + end + + after do + Dependabot::Experiments.reset! + end + describe "::add_handled_dependencies" do subject(:create_dependency_snapshot) do described_class.create_from_job_definition( @@ -92,6 +122,8 @@ ) end + let(:unsupported_error_enabled) { false } + let(:job_definition) do { "base_commit_sha" => base_commit_sha, @@ -148,6 +180,23 @@ ) end + context "when the package manager version is unsupported" do + let(:unsupported_error_enabled) { true } + + let(:job_definition) do + { + "base_commit_sha" => base_commit_sha, + "base64_dependency_files" => encode_dependency_files(dependency_files_for_unsupported) + } + end + + it "raises ToolVersionNotSupported error" do + expect do + create_dependency_snapshot + end.to raise_error(Dependabot::ToolVersionNotSupported) + end + end + context "when the job definition includes valid information prepared by the file fetcher step" do let(:job_definition) do { diff --git a/updater/spec/dependabot/update_files_command_spec.rb b/updater/spec/dependabot/update_files_command_spec.rb index 20a31a06147..db17767e0f6 100644 --- a/updater/spec/dependabot/update_files_command_spec.rb +++ b/updater/spec/dependabot/update_files_command_spec.rb @@ -3,6 +3,7 @@ require "spec_helper" require "dependabot/update_files_command" +require "dependabot/bundler" require "tmpdir" RSpec.describe Dependabot::UpdateFilesCommand do @@ -112,6 +113,32 @@ end end + context "when there is an unsupported package manager version" do + let(:error) do + Dependabot::ToolVersionNotSupported.new( + "bundler", # tool name + "1.0.0", # detected version + ">= 2.0.0" # supported versions + ) + end + + it_behaves_like "a fast-failed job" + + it "records the unsupported version error with details" do + expect(service).to receive(:record_update_job_error).with( + error_type: "tool_version_not_supported", + error_details: { + "tool-name": "bundler", + "detected-version": "1.0.0", + "supported-versions": ">= 2.0.0" + } + ) + expect(service).to receive(:mark_job_as_processed) + + perform_job + end + end + context "with an update files error (cloud)" do let(:error) { StandardError.new("hell") } diff --git a/updater/spec/dependabot/updater/operations/create_security_update_pull_request_spec.rb b/updater/spec/dependabot/updater/operations/create_security_update_pull_request_spec.rb index cadfb1b3f4d..e11778a9e63 100644 --- a/updater/spec/dependabot/updater/operations/create_security_update_pull_request_spec.rb +++ b/updater/spec/dependabot/updater/operations/create_security_update_pull_request_spec.rb @@ -305,29 +305,6 @@ perform end end - - context "when package manager version is unsupported" do - let(:package_manager_version) { "1" } - let(:supported_versions) { %w(2 3) } - - before do - # Enable the feature flag for unsupported version - allow(Dependabot::Experiments).to receive(:enabled?) - .with(:bundler_v1_unsupported_error) - .and_return(true) - - # Ensure unsupported? method returns true so the error is triggered - allow(package_manager).to receive(:unsupported?).and_return(true) - end - - it "logs the ToolVersionNotSupported error to the error handler" do - # Ensure the error handler receives the expected error - expect(mock_error_handler).to receive(:handle_dependency_error) - .with(hash_including(error: instance_of(Dependabot::ToolVersionNotSupported))) - - perform - end - end end describe "#check_and_create_pull_request" do diff --git a/updater/spec/fixtures/bundler/unsupported/Gemfile b/updater/spec/fixtures/bundler/unsupported/Gemfile new file mode 100644 index 00000000000..419f7bed834 --- /dev/null +++ b/updater/spec/fixtures/bundler/unsupported/Gemfile @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem "bundler", "~> 1.15.0" + +gem "business", "~> 1.4.0" +gem "statesman", "~> 1.2.0" diff --git a/updater/spec/fixtures/bundler/unsupported/Gemfile.lock b/updater/spec/fixtures/bundler/unsupported/Gemfile.lock new file mode 100644 index 00000000000..7e5129246ac --- /dev/null +++ b/updater/spec/fixtures/bundler/unsupported/Gemfile.lock @@ -0,0 +1,16 @@ +GEM + remote: https://rubygems.org/ + specs: + business (1.4.0) + statesman (1.2.1) + +PLATFORMS + ruby + +DEPENDENCIES + bundler (~> 1.15.0) + business (~> 1.4.0) + statesman (~> 1.2.0) + +BUNDLED WITH + 1.15.4