Skip to content

Commit

Permalink
add support for legacy index
Browse files Browse the repository at this point in the history
  • Loading branch information
ezekg committed Oct 28, 2024
1 parent b1eef55 commit 171ba2d
Show file tree
Hide file tree
Showing 8 changed files with 790 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def set_package
# users to set --index-url=<keygen>, and we'll redirect non-existent
# packages to PyPI for fulfillment.
#
# TODO(ezekg) Make this configurable?
# TODO(ezekg) make this configurable?
url = URI.parse("https://pypi.org/simple")
pkg = CGI.escape(params[:package])

Expand Down
117 changes: 105 additions & 12 deletions app/controllers/api/v1/release_engines/rubygems/specs_controller.rb
Original file line number Diff line number Diff line change
@@ -1,33 +1,126 @@
# frozen_string_literal: true

require 'rubygems/version'

module Api::V1::ReleaseEngines
class Rubygems::SpecsController < Api::V1::BaseController
include Compression

before_action :scope_to_current_account!
before_action :require_active_subscription!
before_action :authenticate_with_token
before_action :set_artifact
before_action :set_packages, except: %i[quick_gemspec]
before_action :set_artifact, only: %i[quick_gemspec]

def quick
def quick_gemspec
authorize! artifact,
to: :show?

# rubygems expects a marshalled and zlib compressed gemspec
gemspec = artifact.specification.as_gemspec
serialized = Marshal.dump(gemspec)
compressed = Zlib::Deflate.deflate(
serialized,
)
# rubygems expects marshalled and zlib compressed gemspec
gemspec = artifact.specification.as_gemspec
dumped = Marshal.dump(gemspec)
zipped = deflate(dumped)

return unless
stale?(zipped, cache_control: { max_age: 1.day, private: true })

send_data zipped, filename: "#{params[:gem]}.gemspec.rz"
end

def specs
authorize! packages,
to: :index?

artifacts = authorized_scope(current_account.release_artifacts.unyanked.stable.for_packages(packages.ids)).preload(:specification, release: %i[product entitlements constraints])
authorize! artifacts,
to: :index?

# rubygems expects a marshalled and gzipped array of arrays
specs = to_specs(artifacts)
dumped = Marshal.dump(specs)
zipped = gzip(dumped)

return unless
stale?(zipped, cache_control: { max_age: 1.day, private: true })

send_data zipped
end

def latest_specs
authorize! packages,
to: :index?

# use "distinct on" to select latest accessible version per-package and -platform
scoped_artifacts = authorized_scope(current_account.release_artifacts.unyanked.stable.for_packages(packages.ids))
latest_artifacts = ReleaseArtifact.from(
scoped_artifacts.order_by_version.select('releases.release_package_id, release_artifacts.*'),
scoped_artifacts.table_name,
)
.reorder(nil) # remove default order for "distinct on"
.distinct_on(
:release_package_id,
:release_platform_id,
)

artifacts = latest_artifacts.preload(:specification, release: %i[product entitlements constraints])
authorize! artifacts,
to: :index?

specs = to_specs(artifacts)
dumped = Marshal.dump(specs)
zipped = gzip(dumped)

return unless
stale?(zipped, cache_control: { max_age: 1.day, private: true })

send_data zipped
end

def prerelease_specs
authorize! packages,
to: :index?

artifacts = authorized_scope(current_account.release_artifacts.unyanked.prerelease.for_packages(packages.ids)).preload(:specification, release: %i[product entitlements constraints])
authorize! artifacts,
to: :index?

specs = to_specs(artifacts)
dumped = Marshal.dump(specs)
zipped = gzip(dumped)

# for etag support
return unless
stale?(compressed, cache_control: { max_age: 1.day, private: true })
stale?(zipped, cache_control: { max_age: 1.day, private: true })

send_data compressed, filename: "#{params[:gem]}.gemspec.rz"
send_data zipped
end

private

attr_reader :artifact
attr_reader :packages,
:artifact

def to_specs(artifacts)
return [] unless artifacts.present?

specs = artifacts.map do |artifact|
gemspec = artifact.specification.as_gemspec

[gemspec.name, Gem::Version.new(gemspec.version), gemspec.platform.to_s]
end

specs.sort_by(&:third) # platform
.sort_by(&:second) # version
.sort_by(&:first) # name
end

def set_packages
@packages = authorized_scope(apply_scopes(current_account.release_packages.rubygems))
.preload(:product)
.joins(
# we want to ignore packages without any eligible gem specs
releases: { artifacts: :specification },
)
end

def set_artifact
scoped_artifacts = authorized_scope(current_account.release_artifacts.gems)
Expand Down
18 changes: 18 additions & 0 deletions app/controllers/concerns/compression.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module Compression
extend ActiveSupport::Concern

def deflate(data, **) = Zlib::Deflate.deflate(data, **)
def gzip(data, deterministic: true, **)
zipped = StringIO.new
zipped.set_encoding(Encoding::BINARY)

gz = Zlib::GzipWriter.new(zipped, Zlib::BEST_COMPRESSION)
gz.mtime = 0 if deterministic
gz.write(data)
gz.close

zipped.string
end
end
4 changes: 4 additions & 0 deletions app/models/release.rb
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,10 @@ class Release < ApplicationRecord
end
}

scope :for_packages, -> packages {
joins(:package).where(package: { id: packages })
}

scope :for_package, -> package {
case package.presence
when ReleasePackage,
Expand Down
16 changes: 9 additions & 7 deletions app/models/release_artifact.rb
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ class ReleaseArtifact < ApplicationRecord
scope :beta, -> { for_channel_key(%i(stable rc beta)) }
scope :alpha, -> { for_channel_key(%i(stable rc beta alpha)) }
scope :dev, -> { for_channel_key(%i(dev)) }
scope :prerelease, -> { for_channel_key(%i(rc beta alpha dev)) }

scope :accessible_by, -> accessor {
case accessor
Expand Down Expand Up @@ -407,13 +408,14 @@ class ReleaseArtifact < ApplicationRecord

scp = joins(release: { constraints: :entitlement })
scp = if strict
scp.reorder("#{table_name}.created_at": DEFAULT_SORT_ORDER)
.group(:id)
.having(<<~SQL.squish, codes:)
count(release_entitlement_constraints) = count(entitlements) filter (
where entitlements.code in (:codes)
)
SQL
agg = scp.reorder("#{table_name}.created_at": DEFAULT_SORT_ORDER)
.group(:id)

agg.having(<<~SQL.squish, codes:)
count(release_entitlement_constraints) = count(entitlements) filter (
where entitlements.code in (:codes)
)
SQL
else
scp.where(entitlements: { code: codes })
end
Expand Down
7 changes: 5 additions & 2 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,11 @@
end

scope module: :rubygems, defaults: { format: :binary } do
get 'quick/Marshal.4.8/:gem.gemspec.rz', to: 'specs#quick', as: :rubygems_quick_gemspec, constraints: { gem: /[^\/]+/ }
get 'gems/:gem.gem', to: 'gems#show', as: :rubygems_gem, constraints: { gem: /[^\/]+/ }
get 'quick/Marshal.4.8/:gem.gemspec.rz', to: 'specs#quick_gemspec', as: :rubygems_quick_gemspec, constraints: { gem: /[^\/]+/ }
get 'specs.4.8.gz', to: 'specs#specs', as: :rubygems_specs
get 'latest_specs.4.8.gz', to: 'specs#latest_specs', as: :rubygems_latest_specs
get 'prerelease_specs.4.8.gz', to: 'specs#prerelease_specs', as: :rubygems_prerelease_specs
get 'gems/:gem.gem', to: 'gems#show', as: :rubygems_gem, constraints: { gem: /[^\/]+/ }
end
end

Expand Down
Loading

0 comments on commit 171ba2d

Please sign in to comment.