1# frozen_string_literal: true 2 3module Gitlab 4 module Golang 5 PseudoVersion = Struct.new(:semver, :timestamp, :commit_id) 6 7 extend self 8 9 def local_module_prefix 10 @gitlab_prefix ||= "#{Settings.build_gitlab_go_url}/" 11 end 12 13 def semver_tag?(tag) 14 return false if tag.dereferenced_target.nil? 15 16 Packages::SemVer.match?(tag.name, prefixed: true) 17 end 18 19 def pseudo_version?(version) 20 return false unless version 21 22 if version.is_a? String 23 version = parse_semver version 24 return false unless version 25 end 26 27 pre = version.prerelease 28 29 # Valid pseudo-versions are: 30 # vX.0.0-yyyymmddhhmmss-sha1337beef0, when no earlier tagged commit exists for X 31 # vX.Y.Z-pre.0.yyyymmddhhmmss-sha1337beef0, when most recent prior tag is vX.Y.Z-pre 32 # vX.Y.(Z+1)-0.yyyymmddhhmmss-sha1337beef0, when most recent prior tag is vX.Y.Z 33 34 if version.minor != 0 || version.patch != 0 35 m = /\A(.*\.)?0\./.freeze.match pre 36 return false unless m 37 38 pre = pre[m[0].length..] 39 end 40 41 # This pattern is intentionally more forgiving than the patterns 42 # above. Correctness is verified by #validate_pseudo_version. 43 /\A\d{14}-\h+\z/.freeze.match? pre 44 end 45 46 def parse_pseudo_version(semver) 47 # Per Go's implementation of pseudo-versions, a tag should be 48 # considered a pseudo-version if it matches one of the patterns 49 # listed in #pseudo_version?, regardless of the content of the 50 # timestamp or the length of the SHA fragment. However, an error 51 # should be returned if the timestamp is not correct or if the SHA 52 # fragment is not exactly 12 characters long. See also Go's 53 # implementation of: 54 # 55 # - [*codeRepo.validatePseudoVersion](https://github.com/golang/go/blob/daf70d6c1688a1ba1699c933b3c3f04d6f2f73d9/src/cmd/go/internal/modfetch/coderepo.go#L530) 56 # - [Pseudo-version parsing](https://github.com/golang/go/blob/master/src/cmd/go/internal/modfetch/pseudo.go) 57 # - [Pseudo-version request processing](https://github.com/golang/go/blob/master/src/cmd/go/internal/modfetch/coderepo.go) 58 59 # Go ignores anything before '.' or after the second '-', so we will do the same 60 timestamp, commit_id = semver.prerelease.split('-').last 2 61 timestamp = timestamp.split('.').last 62 63 PseudoVersion.new(semver, timestamp, commit_id) 64 end 65 66 def validate_pseudo_version(project, version, commit = nil) 67 commit ||= project.repository.commit_by(oid: version.commit_id) 68 69 # Error messages are based on the responses of proxy.golang.org 70 71 # Verify that the SHA fragment references a commit 72 raise ArgumentError, 'invalid pseudo-version: unknown commit' unless commit 73 74 # Require the SHA fragment to be 12 characters long 75 raise ArgumentError, 'invalid pseudo-version: revision is shorter than canonical' unless version.commit_id.length == 12 76 77 # Require the timestamp to match that of the commit 78 raise ArgumentError, 'invalid pseudo-version: does not match version-control timestamp' unless commit.committed_date.strftime('%Y%m%d%H%M%S') == version.timestamp 79 80 commit 81 end 82 83 def parse_semver(str) 84 Packages::SemVer.parse(str, prefixed: true) 85 end 86 87 def go_path(project, path = nil) 88 if path.blank? 89 "#{local_module_prefix}/#{project.full_path}" 90 else 91 "#{local_module_prefix}/#{project.full_path}/#{path}" 92 end 93 end 94 95 def pkg_go_dev_url(name, version = nil) 96 if version 97 "https://pkg.go.dev/#{name}@#{version}" 98 else 99 "https://pkg.go.dev/#{name}" 100 end 101 end 102 103 def package_url(name, version = nil) 104 return unless UrlSanitizer.valid?("https://#{name}") 105 106 return pkg_go_dev_url(name, version) unless name.starts_with?(local_module_prefix) 107 108 # This will not work if `name` refers to a subdirectory of a project. This 109 # could be expanded with logic similar to Gitlab::Middleware::Go to locate 110 # the project, check for permissions, and return a smarter result. 111 "#{Gitlab.config.gitlab.protocol}://#{name}/" 112 end 113 end 114end 115