class Dependabot::GoModules::FileUpdater::GoModUpdater
Constants
- ENVIRONMENT
Turn off the module proxy for now, as it's causing issues with private git dependencies
- GO_MOD_VERSION
- MODULE_PATH_MISMATCH_REGEXES
- OUT_OF_DISK_REGEXES
- REPO_RESOLVABILITY_ERROR_REGEXES
- RESOLVABILITY_ERROR_REGEXES
Attributes
credentials[R]
dependencies[R]
directory[R]
repo_contents_path[R]
Public Class Methods
new(dependencies:, credentials:, repo_contents_path:, directory:, options:)
click to toggle source
# File lib/dependabot/go_modules/file_updater/go_mod_updater.rb, line 56 def initialize(dependencies:, credentials:, repo_contents_path:, directory:, options:) @dependencies = dependencies @credentials = credentials @repo_contents_path = repo_contents_path @directory = directory @tidy = options.fetch(:tidy, false) @vendor = options.fetch(:vendor, false) end
Public Instance Methods
updated_go_mod_content()
click to toggle source
# File lib/dependabot/go_modules/file_updater/go_mod_updater.rb, line 66 def updated_go_mod_content updated_files[:go_mod] end
updated_go_sum_content()
click to toggle source
# File lib/dependabot/go_modules/file_updater/go_mod_updater.rb, line 70 def updated_go_sum_content updated_files[:go_sum] end
Private Instance Methods
build_module_stubs(stub_paths)
click to toggle source
# File lib/dependabot/go_modules/file_updater/go_mod_updater.rb, line 199 def build_module_stubs(stub_paths) # Create a fake empty module for each local module so that # `go get -d` works, even if some modules have been `replace`d # with a local module that we don't have access to. stub_paths.each do |stub_path| Dir.mkdir(stub_path) unless Dir.exist?(stub_path) FileUtils.touch(File.join(stub_path, "go.mod")) FileUtils.touch(File.join(stub_path, "main.go")) end end
filter_error_message(message:, regex:)
click to toggle source
# File lib/dependabot/go_modules/file_updater/go_mod_updater.rb, line 270 def filter_error_message(message:, regex:) lines = message.lines.select { |l| regex =~ l } return lines.join if lines.any? # In case the regex is multi-line, match the whole string message.match(regex).to_s end
go_mod_path()
click to toggle source
# File lib/dependabot/go_modules/file_updater/go_mod_updater.rb, line 278 def go_mod_path return "go.mod" if directory == "/" File.join(directory, "go.mod") end
handle_subprocess_error(stderr)
click to toggle source
# File lib/dependabot/go_modules/file_updater/go_mod_updater.rb, line 232 def handle_subprocess_error(stderr) # rubocop:disable Metrics/AbcSize stderr = stderr.gsub(Dir.getwd, "") # Package version doesn't match the module major version error_regex = RESOLVABILITY_ERROR_REGEXES.find { |r| stderr =~ r } if error_regex error_message = filter_error_message(message: stderr, regex: error_regex) raise Dependabot::DependencyFileNotResolvable, error_message end if (matches = stderr.match(/Authentication failed for '(?<url>.+)'/)) raise Dependabot::PrivateSourceAuthenticationFailure, matches[:url] end repo_error_regex = REPO_RESOLVABILITY_ERROR_REGEXES.find { |r| stderr =~ r } if repo_error_regex error_message = filter_error_message(message: stderr, regex: repo_error_regex) ResolvabilityErrors.handle(error_message, credentials: credentials) end path_regex = MODULE_PATH_MISMATCH_REGEXES.find { |r| stderr =~ r } if path_regex match = path_regex.match(stderr) raise Dependabot::GoModulePathMismatch. new(go_mod_path, match[1], match[2]) end out_of_disk_regex = OUT_OF_DISK_REGEXES.find { |r| stderr =~ r } if out_of_disk_regex error_message = filter_error_message(message: stderr, regex: out_of_disk_regex) raise Dependabot::OutOfDisk.new, error_message end # We don't know what happened so we raise a generic error msg = stderr.lines.last(10).join.strip raise Dependabot::DependabotError, msg end
in_repo_path(&block)
click to toggle source
# File lib/dependabot/go_modules/file_updater/go_mod_updater.rb, line 191 def in_repo_path(&block) SharedHelpers.in_a_temporary_repo_directory(directory, repo_contents_path) do SharedHelpers.with_git_configured(credentials: credentials) do block.call end end end
parse_manifest()
click to toggle source
# File lib/dependabot/go_modules/file_updater/go_mod_updater.rb, line 183 def parse_manifest command = "go mod edit -json" stdout, stderr, status = Open3.capture3(ENVIRONMENT, command) handle_subprocess_error(stderr) unless status.success? JSON.parse(stdout) || {} end
replace_directive_substitutions(manifest)
click to toggle source
Given a go.mod file, find all `replace` directives pointing to a path on the local filesystem, and return an array of pairs mapping the original path to a hash of the path.
This lets us substitute all parts of the go.mod that are dependent on the layout of the filesystem with a structure we can reproduce (i.e. no paths such as ../../../foo), run the Go tooling, then reverse the process afterwards.
# File lib/dependabot/go_modules/file_updater/go_mod_updater.rb, line 218 def replace_directive_substitutions(manifest) @replace_directive_substitutions ||= Dependabot::GoModules::ReplaceStubber.new(repo_contents_path). stub_paths(manifest, directory) end
run_go_get(dependencies = [])
click to toggle source
# File lib/dependabot/go_modules/file_updater/go_mod_updater.rb, line 159 def run_go_get(dependencies = []) tmp_go_file = "#{SecureRandom.hex}.go" package = Dir.glob("[^\._]*.go").any? do |path| !File.read(path).include?("// +build") end File.write(tmp_go_file, "package dummypkg\n") unless package # TODO: go 1.18 will make `-d` the default behavior, so remove the flag then command = +"go get -d" # `go get` accepts multiple packages, each separated by a space dependencies.each do |dep| version = "v" + dep.version.sub(/^v/i, "") command << " #{dep.name}@#{version}" end command = SharedHelpers.escape_command(command) _, stderr, status = Open3.capture3(ENVIRONMENT, command) handle_subprocess_error(stderr) unless status.success? ensure File.delete(tmp_go_file) if File.exist?(tmp_go_file) end
run_go_mod_tidy()
click to toggle source
# File lib/dependabot/go_modules/file_updater/go_mod_updater.rb, line 139 def run_go_mod_tidy return unless tidy? command = "go mod tidy -e" # we explicitly don't raise an error for 'go mod tidy' and silently # continue here. `go mod tidy` shouldn't block updating versions # because there are some edge cases where it's OK to fail (such as # generated files not available yet to us). Open3.capture3(ENVIRONMENT, command) end
run_go_vendor()
click to toggle source
# File lib/dependabot/go_modules/file_updater/go_mod_updater.rb, line 151 def run_go_vendor return unless vendor? command = "go mod vendor" _, stderr, status = Open3.capture3(ENVIRONMENT, command) handle_subprocess_error(stderr) unless status.success? end
substitute_all(substitutions)
click to toggle source
# File lib/dependabot/go_modules/file_updater/go_mod_updater.rb, line 224 def substitute_all(substitutions) body = substitutions.reduce(File.read("go.mod")) do |text, (a, b)| text.sub(a, b) end write_go_mod(body) end
tidy?()
click to toggle source
# File lib/dependabot/go_modules/file_updater/go_mod_updater.rb, line 288 def tidy? !!@tidy end
update_files()
click to toggle source
# File lib/dependabot/go_modules/file_updater/go_mod_updater.rb, line 83 def update_files # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity in_repo_path do # Map paths in local replace directives to path hashes original_go_mod = File.read("go.mod") original_manifest = parse_manifest original_go_sum = File.read("go.sum") if File.exist?("go.sum") substitutions = replace_directive_substitutions(original_manifest) build_module_stubs(substitutions.values) # Replace full paths with path hashes in the go.mod substitute_all(substitutions) # Bump the deps we want to upgrade using `go get lib@version` run_go_get(dependencies) # Run `go get`'s internal validation checks against _each_ module in `go.mod` # by running `go get` w/o specifying any library. It finds problems like when a # module declares itself using a different name than specified in our `go.mod` etc. run_go_get # If we stubbed modules, don't run `go mod {tidy,vendor}` as # dependencies are incomplete if substitutions.empty? # go mod tidy should run before go mod vendor to ensure any # dependencies removed by go mod tidy are also removed from vendors. run_go_mod_tidy run_go_vendor else substitute_all(substitutions.invert) end updated_go_sum = original_go_sum ? File.read("go.sum") : nil updated_go_mod = File.read("go.mod") # running "go get" may inject the current go version, remove it original_go_version = original_go_mod.match(GO_MOD_VERSION)&.to_a&.first updated_go_version = updated_go_mod.match(GO_MOD_VERSION)&.to_a&.first if original_go_version != updated_go_version go_mod_lines = updated_go_mod.lines go_mod_lines.each_with_index do |line, i| next unless line&.match?(GO_MOD_VERSION) # replace with the original version go_mod_lines[i] = original_go_version # avoid a stranded newline if there was no version originally go_mod_lines[i + 1] = nil if original_go_version.nil? end updated_go_mod = go_mod_lines.compact.join end { go_mod: updated_go_mod, go_sum: updated_go_sum } end end
updated_files()
click to toggle source
# File lib/dependabot/go_modules/file_updater/go_mod_updater.rb, line 79 def updated_files @updated_files ||= update_files end
vendor?()
click to toggle source
# File lib/dependabot/go_modules/file_updater/go_mod_updater.rb, line 292 def vendor? !!@vendor end
write_go_mod(body)
click to toggle source
# File lib/dependabot/go_modules/file_updater/go_mod_updater.rb, line 284 def write_go_mod(body) File.write("go.mod", body) end