Commit d43bff87 authored by Ethan Reesor's avatar Ethan Reesor Committed by Ethan Reesor

Utilize MVVM for Go module proxy

Implement models, entities, presenters
parent db35842f
# frozen_string_literal: true
class Packages::GoModule #< ApplicationRecord
SEMVER_TAG_REGEX = Regexp.new("^#{::Packages::GoModuleVersion::SEMVER_REGEX.source}$").freeze
# belongs_to :project
attr_reader :project, :name, :path, :versions
def initialize(project, name)
@project = project
@name = name
@path =
if @name == package_base
''
elsif @name.start_with?(package_base + '/')
@name[(package_base.length+1)..]
else
nil
end
end
def versions
@versions ||= project.repository.tags.
filter { |tag| SEMVER_TAG_REGEX.match?(tag.name) && !tag.dereferenced_target.nil? }.
map { |tag| ::Packages::GoModuleVersion.new self, tag }.
filter { |ver| ver.valid? }
end
def find_version(name)
versions.filter { |ver| ver.name == name }.first
end
private
def package_base
@package_base ||= Gitlab::Routing.url_helpers.project_url(@project).split('://', 2)[1]
end
end
# frozen_string_literal: true
class Packages::GoModuleVersion #< ApplicationRecord
SEMVER_REGEX = /v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([-.A-Z0-9]+))?(?:\+([-.A-Z0-9]+))?/i.freeze
VERSION_SUFFIX_REGEX = /\/v([1-9]\d*)$/i.freeze
# belongs_to :mod
attr_reader :mod, :tag
delegate :name, to: :tag
def initialize(mod, tag)
@mod = mod
@tag = tag
end
def gomod
return @gomod unless @gomod.nil?
blob = @mod.project.repository.blob_at(tag.dereferenced_target.sha, @mod.path + '/go.mod')
@gomod = blob ? blob.data : ''
end
def valid?
m = gomod.split("\n", 2).first
case major
when 0, 1
m == "module #{@mod.name}"
else
m == "module #{@mod.name}/v#{major}"
end
end
def major
SEMVER_REGEX.match(@tag.name)[1].to_i
end
def minor
SEMVER_REGEX.match(@tag.name)[2].to_i
end
def patch
SEMVER_REGEX.match(@tag.name)[3].to_i
end
def prerelease
SEMVER_REGEX.match(@tag.name)[4]
end
def build
SEMVER_REGEX.match(@tag.name)[5]
end
def files
return @files unless @files.nil?
sha = @tag.dereferenced_target.sha
tree = @mod.project.repository.tree(sha, mod.path, recursive: true).entries.filter { |e| e.file? }
nested = tree.filter { |e| e.name == 'go.mod' && !(mod.path == '' && e.path == 'go.mod' || e.path == mod.path + '/go.mod') }.map { |e| e.path[0..-7] }
@files = tree.filter { |e| !nested.any? { |n| e.path.start_with? n } }
end
def blob_at(path)
@mod.project.repository.blob_at(tag.dereferenced_target.sha, path).data
end
end
# frozen_string_literal: true
module Packages
module Go
class ModuleVersionPresenter
def initialize(version)
@version = version
end
def name
@version.name
end
def time
@version.tag.dereferenced_target.committed_date
end
end
end
end
\ No newline at end of file
......@@ -3,113 +3,89 @@ module API
class GoProxy < Grape::API
helpers ::API::Helpers::PackagesHelpers
SEMVER_REGEX = /v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[-.A-Z0-9]+)?(\+[-.A-Z0-9]+)?/i.freeze
SEMVER_TAG_REGEX = Regexp.new("^#{SEMVER_REGEX.source}$").freeze
MODULE_VERSION_REQUIREMENTS = { :module_version => SEMVER_REGEX }
MODULE_VERSION_REQUIREMENTS = { :module_version => ::Packages::GoModuleVersion::SEMVER_REGEX }
helpers do
def project_package_base
@project_package_base ||= Gitlab::Routing.url_helpers.project_url(user_project).split('://', 2)[1]
end
def check_module_name
def find_module
module_name = params[:module_name].gsub(/![[:alpha:]]/) { |s| s[1..].upcase }
bad_request!('Module Name') if module_name.blank?
if module_name == project_package_base
[module_name, '']
elsif module_name.start_with?(project_package_base + '/')
[module_name, module_name[(project_package_base.length+1)..]]
else
not_found!
end
end
def module_version?(project, path, module_name, tag)
return false unless SEMVER_TAG_REGEX.match?(tag.name)
return false unless tag.dereferenced_target
gomod = project.repository.blob_at(tag.dereferenced_target.sha, path + '/go.mod')
return false unless gomod
mod = gomod.data.split("\n", 2).first
mod == 'module ' + module_name
end
mod = ::Packages::GoModule.new user_project, module_name
not_found! if mod.path.nil?
def module_versions(project, path, module_name)
project.repository.tags.filter { |tag| module_version?(project, path, module_name, tag) }
mod
end
end
params do
requires :id, type: String, desc: 'The ID of a project'
requires :module_name, type: String, desc: 'Module name'
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
namespace ':id/packages/go/*module_name/@v' do
before do
end
desc 'Get all tagged versions for a given Go module' do
detail 'See `go help goproxy`, GET $GOPROXY/<module>/@v/list'
end
get 'list' do
module_name, path = check_module_name
mod = find_module
content_type 'text/plain'
module_versions(user_project, path, module_name).map { |t| t.name }.join("\n")
mod.versions.map { |t| t.name }.join("\n")
end
desc 'Get information about the given module version' do
detail 'See `go help goproxy`, GET $GOPROXY/<module>/@v/<version>.info'
success EE::API::Entities::GoModuleVersion
end
params do
requires :module_version, type: String, desc: 'Module version'
end
get ':module_version.info', :requirements => MODULE_VERSION_REQUIREMENTS do
module_name, path = check_module_name
mod = find_module
tag = user_project.repository.tags.filter { |tag| tag.name == params[:module_version] }.first
not_found! unless tag && module_version?(user_project, path, module_name, tag)
ver = mod.find_version params[:module_version]
not_found! unless ver
{
"Version" => tag.name,
"Time" => tag.dereferenced_target.committed_date
}
present ::Packages::Go::ModuleVersionPresenter.new(ver), with: EE::API::Entities::GoModuleVersion
end
desc 'Get the module file of the given module version' do
detail 'See `go help goproxy`, GET $GOPROXY/<module>/@v/<version>.mod'
end
params do
requires :module_version, type: String, desc: 'Module version'
end
get ':module_version.mod', :requirements => MODULE_VERSION_REQUIREMENTS do
module_name, path = check_module_name
mod = find_module
tag = user_project.repository.tags.filter { |tag| tag.name == params[:module_version] }.first
not_found! unless tag && module_version?(user_project, path, module_name, tag)
ver = mod.find_version params[:module_version]
not_found! unless ver
content_type 'text/plain'
user_project.repository.blob_at(tag.dereferenced_target.sha, path + '/go.mod').data
ver.gomod
end
desc 'Get a zip of the source of the given module version' do
detail 'See `go help goproxy`, GET $GOPROXY/<module>/@v/<version>.zip'
end
params do
requires :module_version, type: String, desc: 'Module version'
end
get ':module_version.zip', :requirements => MODULE_VERSION_REQUIREMENTS do
module_name, path = check_module_name
tag = user_project.repository.tags.filter { |tag| tag.name == params[:module_version] }.first
not_found! unless tag && module_version?(user_project, path, module_name, tag)
mod = find_module
sha = tag.dereferenced_target.sha
tree = user_project.repository.tree(sha, path, recursive: true).entries.filter { |e| e.type == :blob }
nested = tree.filter { |e| e.name == 'go.mod' && !(path == '' && e.path == 'go.mod' || e.path == path + '/go.mod') }.map { |e| e.path[0..-7] }
files = tree.filter { |e| !nested.any? { |n| e.path.start_with? n } }
ver = mod.find_version params[:module_version]
not_found! unless ver
s = Zip::OutputStream.write_buffer do |zip|
files.each do |file|
zip.put_next_entry(file.path)
zip.write user_project.repository.blob_at(sha, file.path).data
ver.files.each do |file|
zip.put_next_entry file.path
zip.write ver.blob_at(file.path)
end
end
header['Content-Disposition'] = ActionDispatch::Http::ContentDisposition.format(disposition: 'attachment', filename: tag.name + '.zip')
header['Content-Disposition'] = ActionDispatch::Http::ContentDisposition.format(disposition: 'attachment', filename: ver.name + '.zip')
header['Content-Transfer-Encoding'] = 'binary'
content_type 'text/plain'
# content_type 'application/zip'
......
# frozen_string_literal: true
module EE
module API
module Entities
class GoModuleVersion < Grape::Entity
expose :name, as: 'Version'
expose :time, as: 'Time'
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment