From 3ac13e6aae07401a9b4dc1453922508d779b367e Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Mon, 24 Feb 2025 17:13:56 +0100 Subject: [PATCH] Add support for an /info_checksums endpoint This is /versions but without the versions list Since bundler/rubygems only use the name/checksum in /versions, we can use a significantly smaller file that is also easier to parse Signed-off-by: Samuel Giddins --- lib/compact_index.rb | 8 ++- lib/compact_index/versions_file.rb | 19 +++---- spec/compact_index_spec.rb | 13 +++++ spec/versions_file_spec.rb | 81 +++++++++++++++++++++++++++++- 4 files changed, 108 insertions(+), 13 deletions(-) diff --git a/lib/compact_index.rb b/lib/compact_index.rb index 78e4bac..b0fc797 100644 --- a/lib/compact_index.rb +++ b/lib/compact_index.rb @@ -21,6 +21,10 @@ def self.names(gem_names) gem_names.join("\n").prepend("---\n") << "\n" end + def self.info_checksums(versions_file, gems = nil, calculate_info_checksums: false) + versions_file.contents(gems, :calculate_info_checksums => calculate_info_checksums) + end + # Returns the versions file content argumented with some extra gems # @param versions_file [CompactIndex::VersionsFile] which will be used as a base response # @param gems [Array] an optional list of [CompactIndex::Gem] to be appended on the end @@ -45,8 +49,8 @@ def self.names(gem_names) # rails 0.0.1,0.1.0 00fd5c36764f4ec1e8adf1c9adaada55 # sinatra 0.1.1,0.1.2,0.1.3 46f0a24d291725736216b4b6e7412be6 # ``` - def self.versions(versions_file, gems = nil, args = {}) - versions_file.contents(gems, args) + def self.versions(versions_file, gems = nil, **kwargs) + versions_file.contents(gems, **kwargs) end # Formats the versions information of a gem, to be display in the `/info/gemname` endpoint. diff --git a/lib/compact_index/versions_file.rb b/lib/compact_index/versions_file.rb index 9571980..07e2d36 100644 --- a/lib/compact_index/versions_file.rb +++ b/lib/compact_index/versions_file.rb @@ -6,14 +6,13 @@ module CompactIndex class VersionsFile - def initialize(file = nil) + def initialize(file = nil, only_info_checksums: false) @path = file || "/versions.list" + @only_info_checksums = only_info_checksums end - def contents(gems = nil, args = {}) - gems = calculate_info_checksums(gems) if args.delete(:calculate_info_checksums) { false } - - raise ArgumentError, "Unknown options: #{args.keys.join(", ")}" unless args.empty? + def contents(gems = nil, calculate_info_checksums: false) + gems = calculate_info_checksums(gems) if calculate_info_checksums File.read(@path).tap do |out| out << gem_lines(gems) if gems @@ -37,10 +36,12 @@ def create(gems, timestamp = Time.now.iso8601) def gem_lines(gems) gems.reduce(+"") do |lines, gem| - version_numbers = gem.versions.map(&:number_and_platform).join(",") - lines << gem.name << - " " << version_numbers << - " #{gem.versions.last.info_checksum}\n" + lines << gem.name + unless @only_info_checksums + version_numbers = gem.versions.map(&:number_and_platform).join(",") + lines << " " << version_numbers + end + lines << " #{gem.versions.last.info_checksum}\n" end end diff --git a/spec/compact_index_spec.rb b/spec/compact_index_spec.rb index 40d864b..ddc8cdf 100644 --- a/spec/compact_index_spec.rb +++ b/spec/compact_index_spec.rb @@ -37,6 +37,19 @@ end end + describe ".info_checksums" do + it "delegates to VersionsFile#content" do + file = Tempfile.new("versions-endpoint") + versions_file = CompactIndex::VersionsFile.new(file.path, :only_info_checksums => true) + gems = [CompactIndex::Gem.new("test", [build_version])] + expect( + CompactIndex.info_checksums(versions_file, gems) + ).to eq( + versions_file.contents(gems) + ) + end + end + describe ".info" do it "without dependencies" do param = [build_version(:number => "1.0.1")] diff --git a/spec/versions_file_spec.rb b/spec/versions_file_spec.rb index 3ee5282..cb3a0fe 100644 --- a/spec/versions_file_spec.rb +++ b/spec/versions_file_spec.rb @@ -104,6 +104,82 @@ end end + context "using the file with only_info_checksums" do + let(:file) { Tempfile.new("create_versions.list") } + let(:gems) do + [ + CompactIndex::Gem.new("gem5", [build_version(:name => "gem5", :number => "1.0.1")]), + CompactIndex::Gem.new("gem2", [ + build_version(:name => "gem2", :number => "1.0.1"), + build_version(:name => "gem2", :number => "1.0.2", :platform => "arch"), + ]), + ] + end + let(:versions_file) { CompactIndex::VersionsFile.new(file.path, :only_info_checksums => true) } + + describe "#create" do + it "writes one line per gem" do + expected_file_output = <<~INDEX + created_at: #{now.iso8601} + --- + gem2 info+gem2+1.0.2 + gem5 info+gem5+1.0.1 + INDEX + versions_file.create(gems) + expect(file.open.read).to eq(expected_file_output) + end + + it "adds the date on top" do + versions_file.create(gems) + expect(file.open.read).to start_with "created_at: #{now.iso8601}\n" + end + + it "orders gems by name" do + file = Tempfile.new("versions-sort") + versions_file = CompactIndex::VersionsFile.new(file.path, :only_info_checksums => true) + gems = [ + CompactIndex::Gem.new("gem_b", [build_version]), + CompactIndex::Gem.new("gem_a", [build_version]), + ] + versions_file.create(gems) + expect(file.open.read).to eq(<<~INDEX) + created_at: #{now.iso8601} + --- + gem_a info+test_gem+1.0 + gem_b info+test_gem+1.0 + INDEX + end + + it "uses the given version order" do + file = Tempfile.new("versions-sort") + versions_file = CompactIndex::VersionsFile.new(file.path, :only_info_checksums => true) + gems = [ + CompactIndex::Gem.new("test", + [ + build_version(:number => "1.3.0"), + build_version(:number => "2.2"), + build_version(:number => "1.1.1"), + build_version(:number => "1.1.1"), + build_version(:number => "2.1.2"), + ]), + ] + versions_file.create(gems) + expect(file.open.read).to include("test info+test_gem+2.1.2") + end + end + + context "create with ts" do + file = Tempfile.new + versions_file = CompactIndex::VersionsFile.new(file.path) + ts = Time.new(1999, 9, 9).iso8601 + + it "is used in created_at header" do + versions_file.create([], ts) + expect(file.open.read).to start_with("created_at: #{ts}") + end + end + end + describe "#updated_at" do it "is epoch start when file does not exist" do expect(CompactIndex::VersionsFile.new("/tmp/doesntexist").updated_at).to eq(Time.at(0).to_datetime) @@ -114,9 +190,10 @@ end it "is the created_at time when the header exists" do - Tempfile.new("created_at_versions") do |tmp| + Tempfile.create("created_at_versions") do |tmp| tmp.write("created_at: 2015-08-23T17:22:53-07:00\n---\ngem2 1.0.1\n") - file = CompactIndex::VersionsFile.new(tmp.path).updated_at + tmp.rewind + file = CompactIndex::VersionsFile.new(tmp.path) expect(file.updated_at).to eq(DateTime.parse("2015-08-23T17:22:53-07:00")) end end