Commit be027f87 authored by Ethan Reesor's avatar Ethan Reesor

Implement go.mod viewer

parent a9c1b0c1
......@@ -58,6 +58,7 @@ class Blob < SimpleDelegator
BlobViewer::Gemfile,
BlobViewer::Gemspec,
BlobViewer::GodepsJson,
BlobViewer::GoMod,
BlobViewer::PackageJson,
BlobViewer::Podfile,
BlobViewer::Podspec,
......
# frozen_string_literal: true
module BlobViewer
class GoMod < DependencyManager
include ServerSide
self.file_types = %i(go_mod go_sum)
def manager_name
'Go Modules'
end
def manager_url
'https://golang.org/ref/mod'
end
def package_type
'go'
end
def package_name
return if blob.name != 'go.mod'
return @package_name unless @package_name.nil?
return unless blob.data.starts_with? 'module '
@package_name ||= blob.data.partition("\n").first[7..]
end
def package_url
return unless Gitlab::UrlSanitizer.valid?("https://#{package_name}")
if package_name.starts_with? Settings.build_gitlab_go_url + '/'
"#{Gitlab.config.gitlab.protocol}://#{package_name}"
else
"https://pkg.go.dev/#{package_name}"
end
end
end
end
---
title: Add viewer and linker for go.mod and go.sum
merge_request: 28262
author: Ethan Reesor @firelizzard
type: added
......@@ -13,7 +13,9 @@ module Gitlab
CartfileLinker,
GodepsJsonLinker,
RequirementsTxtLinker,
CargoTomlLinker
CargoTomlLinker,
GoModLinker,
GoSumLinker
].freeze
def self.linker(blob_name)
......
......@@ -63,6 +63,10 @@ module Gitlab
"https://github.com/#{name}"
end
def pkg_go_dev_url(name)
"https://pkg.go.dev/#{name}"
end
def link_tag(name, url)
sanitize(
%{<a href="#{ERB::Util.html_escape_once(url)}" rel="nofollow noreferrer noopener" target="_blank">#{ERB::Util.html_escape_once(name)}</a>},
......
# frozen_string_literal: true
module Gitlab
module DependencyLinker
class GoModLinker < BaseLinker
self.file_type = :go_mod
private
SEMVER = /
v (?# prefix)
(0|[1-9]\d*) (?# major)
\.(0|[1-9]\d*) (?# minor)
\.(0|[1-9]\d*) (?# patch)
(?:-((?:\d*[a-zA-Z\-][0-9a-zA-Z\-]*|0|[1-9]\d*)(?:\.(?:\d*[a-zA-Z-][0-9a-zA-Z-]*|0|[1-9]\d*))*))? (?# prerelease)
(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))? (?# build)
/ix.freeze
NAME = Gitlab::Regex.go_package_regex
REGEX = Regexp.new("(?<name>#{NAME.source})(?:\\s+(?<version>#{SEMVER.source}))?", NAME.options).freeze
def package_url(name, version = nil)
return unless Gitlab::UrlSanitizer.valid?("https://#{name}")
if name.starts_with?(Settings.build_gitlab_go_url + '/')
"#{Gitlab.config.gitlab.protocol}://#{name}"
else
url = pkg_go_dev_url(name)
url += "@#{version}" if version
url
end
end
# rubocop: disable CodeReuse/ActiveRecord
def link_dependencies
highlighted_lines.map!.with_index do |rich_line, i|
plain_line = plain_lines[i].chomp
match = REGEX.match(plain_line)
next rich_line unless match
i, j = match.offset(:name)
marker = StringRangeMarker.new(plain_line, rich_line.html_safe)
marker.mark([i..(j - 1)]) do |text, left:, right:|
url = package_url(text, match[:version])
url ? link_tag(text, url) : text
end
end
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
end
# frozen_string_literal: true
module Gitlab
module DependencyLinker
class GoSumLinker < GoModLinker
self.file_type = :go_sum
private
BASE64 = /(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?/.freeze
REGEX = Regexp.new("^(?<name>#{NAME.source})\\s+(?<version>#{SEMVER.source})(\/go.mod)?\\s+h1:(?<checksum>#{BASE64.source})$", NAME.options).freeze
# rubocop: disable CodeReuse/ActiveRecord
def link_dependencies
highlighted_lines.map!.with_index do |rich_line, i|
plain_line = plain_lines[i].chomp
match = REGEX.match(plain_line)
next rich_line unless match
i0, j0 = match.offset(:name)
i2, j2 = match.offset(:checksum)
marker = StringRangeMarker.new(plain_line, rich_line.html_safe)
marker.mark([i0..(j0 - 1), i2..(j2 - 1)]) do |text, left:, right:|
if left
url = package_url(text, match[:version])
url ? link_tag(text, url) : text
elsif right
link_tag(text, "https://sum.golang.org/lookup/#{match[:name]}@#{match[:version]}")
end
end
end
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
end
......@@ -32,6 +32,8 @@ module Gitlab
gemfile_lock: 'Gemfile.lock',
gemspec: %r{\A[^/]*\.gemspec\z},
godeps_json: 'Godeps.json',
go_mod: 'go.mod',
go_sum: 'go.sum',
package_json: 'package.json',
podfile: 'Podfile',
podspec_json: %r{\A[^/]*\.podspec\.json\z},
......
......@@ -215,6 +215,24 @@ module Gitlab
def issue
@issue ||= /(?<issue>\d+\b)/
end
def go_package_regex
# A Go package name looks like a URL but is not; it:
# - Must not have a scheme, such as http:// or https://
# - Must not have a port number, such as :8080 or :8443
@go_package_regex ||= /
\b (?# word boundary)
[0-9a-z]((-|[0-9a-z]){0,61}[0-9a-z])? (?# first domain)
(\.[0-9a-z]((-|[0-9a-z]){0,61}[0-9a-z])?)* (?# inner domains)
\.[a-z]{2,} (?# top-level domain)
(\/(
[-\/$_.+!*'(),0-9a-z] (?# plain URL character)
| %[0-9a-f]{2})* (?# URL encoded character)
)? (?# path)
\b (?# word boundary)
/ix.freeze
end
end
end
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::DependencyLinker::GoModLinker do
let(:file_name) { 'go.mod' }
let(:file_content) do
<<-CONTENT.strip_heredoc
module gitlab.com/gitlab-org/gitlab-workhorse
go 1.12
require (
github.com/BurntSushi/toml v0.3.1
github.com/FZambia/sentinel v1.0.0
github.com/alecthomas/chroma v0.7.3
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/getsentry/raven-go v0.1.2
github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721
github.com/golang/protobuf v1.3.2
github.com/gomodule/redigo v2.0.0+incompatible
github.com/gorilla/websocket v1.4.0
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/jfbus/httprs v0.0.0-20190827093123-b0af8319bb15
github.com/jpillora/backoff v0.0.0-20170918002102-8eab2debe79d
github.com/prometheus/client_golang v1.0.0
github.com/rafaeljusto/redigomock v0.0.0-20190202135759-257e089e14a1
github.com/sebest/xff v0.0.0-20160910043805-6c115e0ffa35
github.com/sirupsen/logrus v1.3.0
github.com/stretchr/testify v1.5.1
gitlab.com/gitlab-org/gitaly v1.74.0
gitlab.com/gitlab-org/labkit v0.0.0-20200520155818-96e583c57891
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa
golang.org/x/tools v0.0.0-20200117161641-43d50277825c
google.golang.org/grpc v1.24.0
gopkg.in/yaml.v2 v2.2.8 // indirect
honnef.co/go/tools v0.0.1-2019.2.3
)
CONTENT
end
describe '.support?' do
it 'supports go.mod' do
expect(described_class.support?('go.mod')).to be_truthy
end
it 'does not support other files' do
expect(described_class.support?('go.mod.example')).to be_falsey
end
end
describe '#link' do
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
end
it 'links the module name' do
expect(subject).to include(link('gitlab.com/gitlab-org/gitlab-workhorse', 'https://pkg.go.dev/gitlab.com/gitlab-org/gitlab-workhorse'))
end
it 'links dependencies' do
expect(subject).to include(link('github.com/BurntSushi/toml', 'https://pkg.go.dev/github.com/BurntSushi/toml@v0.3.1'))
expect(subject).to include(link('github.com/FZambia/sentinel', 'https://pkg.go.dev/github.com/FZambia/sentinel@v1.0.0'))
expect(subject).to include(link('github.com/alecthomas/chroma', 'https://pkg.go.dev/github.com/alecthomas/chroma@v0.7.3'))
expect(subject).to include(link('github.com/dgrijalva/jwt-go', 'https://pkg.go.dev/github.com/dgrijalva/jwt-go@v3.2.0+incompatible'))
expect(subject).to include(link('github.com/getsentry/raven-go', 'https://pkg.go.dev/github.com/getsentry/raven-go@v0.1.2'))
expect(subject).to include(link('github.com/golang/gddo', 'https://pkg.go.dev/github.com/golang/gddo@v0.0.0-20190419222130-af0f2af80721'))
expect(subject).to include(link('github.com/golang/protobuf', 'https://pkg.go.dev/github.com/golang/protobuf@v1.3.2'))
expect(subject).to include(link('github.com/gomodule/redigo', 'https://pkg.go.dev/github.com/gomodule/redigo@v2.0.0+incompatible'))
expect(subject).to include(link('github.com/gorilla/websocket', 'https://pkg.go.dev/github.com/gorilla/websocket@v1.4.0'))
expect(subject).to include(link('github.com/grpc-ecosystem/go-grpc-middleware', 'https://pkg.go.dev/github.com/grpc-ecosystem/go-grpc-middleware@v1.0.0'))
expect(subject).to include(link('github.com/grpc-ecosystem/go-grpc-prometheus', 'https://pkg.go.dev/github.com/grpc-ecosystem/go-grpc-prometheus@v1.2.0'))
expect(subject).to include(link('github.com/jfbus/httprs', 'https://pkg.go.dev/github.com/jfbus/httprs@v0.0.0-20190827093123-b0af8319bb15'))
expect(subject).to include(link('github.com/jpillora/backoff', 'https://pkg.go.dev/github.com/jpillora/backoff@v0.0.0-20170918002102-8eab2debe79d'))
expect(subject).to include(link('github.com/prometheus/client_golang', 'https://pkg.go.dev/github.com/prometheus/client_golang@v1.0.0'))
expect(subject).to include(link('github.com/rafaeljusto/redigomock', 'https://pkg.go.dev/github.com/rafaeljusto/redigomock@v0.0.0-20190202135759-257e089e14a1'))
expect(subject).to include(link('github.com/sebest/xff', 'https://pkg.go.dev/github.com/sebest/xff@v0.0.0-20160910043805-6c115e0ffa35'))
expect(subject).to include(link('github.com/sirupsen/logrus', 'https://pkg.go.dev/github.com/sirupsen/logrus@v1.3.0'))
expect(subject).to include(link('github.com/stretchr/testify', 'https://pkg.go.dev/github.com/stretchr/testify@v1.5.1'))
expect(subject).to include(link('gitlab.com/gitlab-org/gitaly', 'https://pkg.go.dev/gitlab.com/gitlab-org/gitaly@v1.74.0'))
expect(subject).to include(link('gitlab.com/gitlab-org/labkit', 'https://pkg.go.dev/gitlab.com/gitlab-org/labkit@v0.0.0-20200520155818-96e583c57891'))
expect(subject).to include(link('golang.org/x/lint', 'https://pkg.go.dev/golang.org/x/lint@v0.0.0-20191125180803-fdd1cda4f05f'))
expect(subject).to include(link('golang.org/x/net', 'https://pkg.go.dev/golang.org/x/net@v0.0.0-20200114155413-6afb5195e5aa'))
expect(subject).to include(link('golang.org/x/tools', 'https://pkg.go.dev/golang.org/x/tools@v0.0.0-20200117161641-43d50277825c'))
expect(subject).to include(link('google.golang.org/grpc', 'https://pkg.go.dev/google.golang.org/grpc@v1.24.0'))
expect(subject).to include(link('gopkg.in/yaml.v2', 'https://pkg.go.dev/gopkg.in/yaml.v2@v2.2.8'))
expect(subject).to include(link('honnef.co/go/tools', 'https://pkg.go.dev/honnef.co/go/tools@v0.0.1-2019.2.3'))
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::DependencyLinker::GoSumLinker do
let(:file_name) { 'go.sum' }
let(:file_content) do
<<-CONTENT.strip_heredoc
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
gitlab.com/go-utils/io v0.0.0-20190408212915-156add3f8f97 h1:9EKx8vX3kJzyj977yiWB8iIOXHyvbg8SmfOScw7OcN0=
gitlab.com/go-utils/io v0.0.0-20190408212915-156add3f8f97/go.mod h1:cF4ez5kIKPWU1BB1Z4qgu6dQkT3pvknXff8PSlGaNo8=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
CONTENT
end
describe '.support?' do
it 'supports go.sum' do
expect(described_class.support?('go.sum')).to be_truthy
end
it 'does not support other files' do
expect(described_class.support?('go.sum.example')).to be_falsey
end
end
describe '#link' do
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
end
it 'links modules' do
expect(subject).to include(link('github.com/davecgh/go-spew', 'https://pkg.go.dev/github.com/davecgh/go-spew@v1.1.0'))
expect(subject).to include(link('github.com/pmezard/go-difflib', 'https://pkg.go.dev/github.com/pmezard/go-difflib@v1.0.0'))
expect(subject).to include(link('github.com/stretchr/objx', 'https://pkg.go.dev/github.com/stretchr/objx@v0.1.0'))
expect(subject).to include(link('github.com/stretchr/testify', 'https://pkg.go.dev/github.com/stretchr/testify@v1.3.0'))
expect(subject).to include(link('gitlab.com/go-utils/io', 'https://pkg.go.dev/gitlab.com/go-utils/io@v0.0.0-20190408212915-156add3f8f97'))
expect(subject).to include(link('golang.org/x/xerrors', 'https://pkg.go.dev/golang.org/x/xerrors@v0.0.0-20190717185122-a985d3407aa7'))
end
it 'links checksums' do
expect(subject).to include(link('ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=', 'https://sum.golang.org/lookup/github.com/davecgh/go-spew@v1.1.0'))
expect(subject).to include(link('J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=', 'https://sum.golang.org/lookup/github.com/davecgh/go-spew@v1.1.0'))
expect(subject).to include(link('4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=', 'https://sum.golang.org/lookup/github.com/pmezard/go-difflib@v1.0.0'))
expect(subject).to include(link('iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=', 'https://sum.golang.org/lookup/github.com/pmezard/go-difflib@v1.0.0'))
expect(subject).to include(link('4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=', 'https://sum.golang.org/lookup/github.com/stretchr/objx@v0.1.0'))
expect(subject).to include(link('HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=', 'https://sum.golang.org/lookup/github.com/stretchr/objx@v0.1.0'))
expect(subject).to include(link('TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=', 'https://sum.golang.org/lookup/github.com/stretchr/testify@v1.3.0'))
expect(subject).to include(link('M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=', 'https://sum.golang.org/lookup/github.com/stretchr/testify@v1.3.0'))
expect(subject).to include(link('9EKx8vX3kJzyj977yiWB8iIOXHyvbg8SmfOScw7OcN0=', 'https://sum.golang.org/lookup/gitlab.com/go-utils/io@v0.0.0-20190408212915-156add3f8f97'))
expect(subject).to include(link('cF4ez5kIKPWU1BB1Z4qgu6dQkT3pvknXff8PSlGaNo8=', 'https://sum.golang.org/lookup/gitlab.com/go-utils/io@v0.0.0-20190408212915-156add3f8f97'))
expect(subject).to include(link('9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=', 'https://sum.golang.org/lookup/golang.org/x/xerrors@v0.0.0-20190717185122-a985d3407aa7'))
expect(subject).to include(link('I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=', 'https://sum.golang.org/lookup/golang.org/x/xerrors@v0.0.0-20190717185122-a985d3407aa7'))
end
end
end
......@@ -91,5 +91,21 @@ describe Gitlab::DependencyLinker do
described_class.link(blob_name, nil, nil)
end
it 'links using GoModLinker' do
blob_name = 'go.mod'
expect(described_class::GoModLinker).to receive(:link)
described_class.link(blob_name, nil, nil)
end
it 'links using GoSumLinker' do
blob_name = 'go.sum'
expect(described_class::GoSumLinker).to receive(:link)
described_class.link(blob_name, nil, nil)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
describe Gitlab::Regex do
shared_examples_for 'project/group name regex' do
......@@ -274,4 +274,14 @@ describe Gitlab::Regex do
it { is_expected.not_to match('../../../../../1.2.3') }
it { is_expected.not_to match('%2e%2e%2f1.2.3') }
end
describe '.go_package_regex' do
subject { described_class.go_package_regex }
it { is_expected.to match('example.com') }
it { is_expected.to match('example.com/foo') }
it { is_expected.to match('example.com/foo/bar') }
it { is_expected.to match('example.com/foo/bar/baz') }
it { is_expected.to match('tl.dr.foo.bar.baz') }
end
end
# frozen_string_literal: true
require 'spec_helper'
describe BlobViewer::GoMod do
include FakeBlobHelpers
let(:project) { build_stubbed(:project) }
let(:data) do
<<-SPEC.strip_heredoc
module #{Settings.build_gitlab_go_url}/#{project.full_path}
SPEC
end
let(:blob) { fake_blob(path: 'go.mod', data: data) }
subject { described_class.new(blob) }
describe '#package_name' do
it 'returns the package name' do
expect(subject.package_name).to eq("#{Settings.build_gitlab_go_url}/#{project.full_path}")
end
end
describe '#package_url' do
it 'returns the package URL' do
expect(subject.package_url).to eq("#{Gitlab.config.gitlab.protocol}://#{Settings.build_gitlab_go_url}/#{project.full_path}")
end
context 'when the homepage has an invalid URL' do
let(:data) do
<<-SPEC.strip_heredoc
module javascript:alert()
SPEC
end
it 'returns nil' do
expect(subject.package_url).to be_nil
end
end
end
describe '#package_type' do
it 'returns "package"' do
expect(subject.package_type).to eq('go')
end
end
context 'when the module name does not start with the instance URL' do
let(:data) do
<<-SPEC.strip_heredoc
module example.com/foo/bar
SPEC
end
subject { described_class.new(blob) }
describe '#package_url' do
it 'returns the pkg.go.dev URL' do
expect(subject.package_url).to eq("https://pkg.go.dev/example.com/foo/bar")
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