Skip to content
Closed
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
1 change: 1 addition & 0 deletions app/controllers/api/v1/versions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ def index
@registry = Registry.find_by_name!(params[:registry_id])
@package = find_package_with_normalization!(@registry, params[:package_id])
scope = @package.versions#.includes(:dependencies)
scope = scope.with_omnibor_artifact_id(params[:omnibor_artifact_id]) if params[:omnibor_artifact_id].present?

scope = scope.created_after(params[:created_after]) if params[:created_after].present?
scope = scope.published_after(params[:published_after]) if params[:published_after].present?
Expand Down
32 changes: 32 additions & 0 deletions app/models/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ class Version < ApplicationRecord
validates_presence_of :package_id, :number
validates_uniqueness_of :number, scope: :package_id, case_sensitive: false

before_validation :generate_omnibor_artifact_id

belongs_to :package
belongs_to :registry, optional: true
counter_culture :package
Expand All @@ -16,6 +18,7 @@ def self.sortable_columns
'created_at' => 'created_at',
'updated_at' => 'updated_at',
'number' => 'number',
'omnibor_artifact_id' => 'omnibor_artifact_id',
}
end

Expand All @@ -25,6 +28,7 @@ def self.sortable_columns
scope :updated_after, ->(updated_at) { where('updated_at > ?', updated_at) }
scope :created_before, ->(created_at) { where('created_at < ?', created_at) }
scope :updated_before, ->(updated_at) { where('updated_at < ?', updated_at) }
scope :with_omnibor_artifact_id, ->(artifact_id) { where(omnibor_artifact_id: artifact_id) }

scope :active, -> { where(status: nil) }

Expand Down Expand Up @@ -214,6 +218,34 @@ def prerelease?
end
end

def generate_omnibor_artifact_id
return if omnibor_artifact_id.present?

self.omnibor_artifact_id = calculated_omnibor_artifact_id
end

def calculated_omnibor_artifact_id
length = metadata_artifact_length
sha256 = integrity_sha256
return if length.blank? || sha256.blank?

digest = Digest::SHA256.hexdigest("blob #{length}\0" + [sha256].pack('H*'))
"gitoid:blob:sha256:#{digest}"
end

def metadata_artifact_length
value = metadata&.dig('length') || metadata&.dig(:length) || metadata&.dig('size') || metadata&.dig(:size) || metadata&.dig('archive_length') || metadata&.dig(:archive_length)
value.to_i if value.present? && value.to_i.positive?
end

def integrity_sha256
return unless integrity.present?

integrity.to_s.delete_prefix('sha256-').then do |value|
value.match?(/\A[0-9a-f]{64}\z/i) ? value.downcase : nil
end
end

def transitive_dependencies(max_depth: TransitiveDependencyResolver::DEFAULT_MAX_DEPTH, max_dependencies: TransitiveDependencyResolver::DEFAULT_MAX_DEPENDENCIES, include_optional: false, kind: nil)
resolver = TransitiveDependencyResolver.for_ecosystem(package.ecosystem).new(
self,
Expand Down
2 changes: 1 addition & 1 deletion app/views/api/v1/versions/_version.json.jbuilder
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
json.extract! version, :id, :number, :published_at, :licenses, :integrity, :status, :download_url, :registry_url, :documentation_url, :install_command, :metadata, :created_at, :updated_at, :purl, :related_tag, :latest
json.extract! version, :id, :number, :published_at, :licenses, :integrity, :omnibor_artifact_id, :status, :download_url, :registry_url, :documentation_url, :install_command, :metadata, :created_at, :updated_at, :purl, :related_tag, :latest
json.version_url api_v1_registry_package_version_url(version.package.registry, version.package, version)
json.codemeta_url codemeta_api_v1_registry_package_version_url(version.package.registry, version.package, version)
json.dependencies version.dependencies do |dependency|
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
json.extract! version, :id, :number, :published_at, :licenses, :integrity, :status, :download_url, :registry_url, :documentation_url, :install_command, :metadata, :created_at, :updated_at, :purl, :related_tag, :latest
json.extract! version, :id, :number, :published_at, :licenses, :integrity, :omnibor_artifact_id, :status, :download_url, :registry_url, :documentation_url, :install_command, :metadata, :created_at, :updated_at, :purl, :related_tag, :latest
json.version_url api_v1_registry_package_version_url(@registry, version.package, version)
json.package_url api_v1_registry_package_url(@registry, version.package)
2 changes: 1 addition & 1 deletion app/views/api/v1/versions/index.json.jbuilder
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
json.array! @versions do |version|
json.extract! version, :id, :number, :published_at, :licenses, :integrity, :status, :download_url, :registry_url, :documentation_url, :install_command, :metadata, :created_at, :updated_at, :purl, :related_tag, :latest
json.extract! version, :id, :number, :published_at, :licenses, :integrity, :omnibor_artifact_id, :status, :download_url, :registry_url, :documentation_url, :install_command, :metadata, :created_at, :updated_at, :purl, :related_tag, :latest
json.version_url api_v1_registry_package_version_url(@registry, @package, version)
# json.dependencies version.dependencies do |dependency|
# json.extract! dependency, :ecosystem, :package_name, :requirements, :kind, :optional
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class AddOmniborArtifactIdToVersions < ActiveRecord::Migration[7.0]
def change
add_column :versions, :omnibor_artifact_id, :string
add_index :versions, :omnibor_artifact_id, unique: true, where: "omnibor_artifact_id IS NOT NULL"
end
end
17 changes: 15 additions & 2 deletions test/controllers/api/v1/versions_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class ApiV1VersionsControllerTest < ActionDispatch::IntegrationTest
@registry = Registry.create(name: 'crates.io', url: 'https://crates.io', ecosystem: 'cargo')
@package = @registry.packages.create(ecosystem: 'cargo', name: 'rand')
@version = @package.versions.create(number: '1.0.0', metadata: {foo: 'bar'}, registry_id: @registry.id)
@omnibor_version = @package.versions.create(number: '2.0.0', integrity: 'sha256-2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824', metadata: { length: 5 }, registry_id: @registry.id)

@pypi_registry = Registry.create(name: 'pypi.org', url: 'https://pypi.org', ecosystem: 'pypi')
@pypi_package = @pypi_registry.packages.create(
Expand All @@ -22,7 +23,19 @@ class ApiV1VersionsControllerTest < ActionDispatch::IntegrationTest

actual_response = Oj.load(@response.body)

assert_equal actual_response.length, 1
assert_equal actual_response.length, 2
end

test 'filter versions by OmniBOR artifact ID' do
artifact_id = 'gitoid:blob:sha256:2c0eb59d2f7fb34d4326d83952c8425731a200dde6b6f93c465c6e315a4cbd33'
get api_v1_registry_package_versions_path(registry_id: @registry.name, package_id: @package.name, omnibor_artifact_id: artifact_id)
assert_response :success

actual_response = Oj.load(@response.body)

assert_equal 1, actual_response.length
assert_equal '2.0.0', actual_response.first['number']
assert_equal artifact_id, actual_response.first['omnibor_artifact_id']
end

test 'get version of a package' do
Expand Down Expand Up @@ -179,7 +192,7 @@ class ApiV1VersionsControllerTest < ActionDispatch::IntegrationTest
assert_response :success

actual_response = Oj.load(@response.body)
assert_equal actual_response.length, 1
assert_equal actual_response.length, 2
assert_equal actual_response.first['number'], '1.0.0'
end

Expand Down
21 changes: 21 additions & 0 deletions test/models/version_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,27 @@ class VersionTest < ActiveSupport::TestCase
assert Purl.parse(@version.purl)
end



test 'generate_omnibor_artifact_id from sha256 integrity and length metadata' do
version = @package.versions.create(
number: '3.0.0',
integrity: 'sha256-2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824',
metadata: { length: 5 }
)

assert_equal 'gitoid:blob:sha256:2c0eb59d2f7fb34d4326d83952c8425731a200dde6b6f93c465c6e315a4cbd33', version.omnibor_artifact_id
end

test 'generate_omnibor_artifact_id ignores missing length metadata' do
version = @package.versions.create(
number: '4.0.0',
integrity: 'sha256-2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'
)

assert_nil version.omnibor_artifact_id
end

test "transitive_dependencies delegates to resolver" do
TransitiveDependencyResolver.any_instance.expects(:resolve).returns([])

Expand Down