module Dependabot::SharedHelpers
Constants
- GIT_CONFIG_GLOBAL_PATH
- SIGKILL
- USER_AGENT
Public Class Methods
configure_git_to_use_https(host)
click to toggle source
rubocop:enable Metrics/AbcSize rubocop:enable Metrics/PerceivedComplexity
# File lib/dependabot/shared_helpers.rb, line 226 def self.configure_git_to_use_https(host) # NOTE: we use --global here (rather than --system) so that Dependabot # can be run without privileged access run_shell_command( "git config --global --replace-all url.https://#{host}/."\ "insteadOf ssh://git@#{host}/" ) run_shell_command( "git config --global --add url.https://#{host}/."\ "insteadOf ssh://git@#{host}:" ) run_shell_command( "git config --global --add url.https://#{host}/."\ "insteadOf git@#{host}:" ) run_shell_command( "git config --global --add url.https://#{host}/."\ "insteadOf git@#{host}/" ) run_shell_command( "git config --global --add url.https://#{host}/."\ "insteadOf git://#{host}/" ) end
configure_git_to_use_https_with_credentials(credentials)
click to toggle source
rubocop:disable Metrics/AbcSize rubocop:disable Metrics/PerceivedComplexity
# File lib/dependabot/shared_helpers.rb, line 173 def self.configure_git_to_use_https_with_credentials(credentials) File.open(GIT_CONFIG_GLOBAL_PATH, "w") do |file| file << "# Generated by dependabot/dependabot-core" end # Then add a file-based credential store that loads a file in this repo. # Under the hood this uses git credential-store, but it's invoked through # a wrapper binary that only allows non-mutating commands. Without this, # whenever the credentials are deemed to be invalid, they're erased. run_shell_command( "git config --global credential.helper "\ "'!#{credential_helper_path} --file #{Dir.pwd}/git.store'", allow_unsafe_shell_command: true ) github_credentials = credentials. select { |c| c["type"] == "git_source" }. select { |c| c["host"] == "github.com" }. select { |c| c["password"] && c["username"] } # If multiple credentials are specified for github.com, pick the one that # *isn't* just an app token (since it must have been added deliberately) github_credential = github_credentials.find { |c| !c["password"]&.start_with?("v1.") } || github_credentials.first # Make sure we always have https alternatives for github.com. configure_git_to_use_https("github.com") if github_credential.nil? deduped_credentials = credentials - github_credentials + [github_credential].compact # Build the content for our credentials file git_store_content = "" deduped_credentials.each do |cred| next unless cred["type"] == "git_source" next unless cred["username"] && cred["password"] authenticated_url = "https://#{cred.fetch('username')}:#{cred.fetch('password')}"\ "@#{cred.fetch('host')}" git_store_content += authenticated_url + "\n" configure_git_to_use_https(cred.fetch("host")) end # Save the file File.write("git.store", git_store_content) end
credential_helper_path()
click to toggle source
# File lib/dependabot/shared_helpers.rb, line 167 def self.credential_helper_path File.join(__dir__, "../../bin/git-credential-store-immutable") end
escape_command(command)
click to toggle source
Escapes all special characters, e.g. = & | <>
# File lib/dependabot/shared_helpers.rb, line 67 def self.escape_command(command) command_parts = command.split.map(&:strip).reject(&:empty?) Shellwords.join(command_parts) end
excon_defaults(options = nil)
click to toggle source
# File lib/dependabot/shared_helpers.rb, line 144 def self.excon_defaults(options = nil) options ||= {} headers = options.delete(:headers) { connect_timeout: 5, write_timeout: 5, read_timeout: 20, omit_default_port: true, middlewares: excon_middleware, headers: excon_headers(headers) }.merge(options) end
excon_headers(headers = nil)
click to toggle source
# File lib/dependabot/shared_helpers.rb, line 137 def self.excon_headers(headers = nil) headers ||= {} { "User-Agent" => USER_AGENT }.merge(headers) end
excon_middleware()
click to toggle source
rubocop:enable Metrics/MethodLength
# File lib/dependabot/shared_helpers.rb, line 131 def self.excon_middleware Excon.defaults[:middlewares] + [Excon::Middleware::Decompress] + [Excon::Middleware::RedirectFollower] end
in_a_temporary_directory(directory = "/") { |path| ... }
click to toggle source
# File lib/dependabot/shared_helpers.rb, line 41 def self.in_a_temporary_directory(directory = "/") Dir.mkdir(Utils::BUMP_TMP_DIR_PATH) unless Dir.exist?(Utils::BUMP_TMP_DIR_PATH) Dir.mktmpdir(Utils::BUMP_TMP_FILE_PREFIX, Utils::BUMP_TMP_DIR_PATH) do |dir| path = Pathname.new(File.join(dir, directory)).expand_path FileUtils.mkpath(path) Dir.chdir(path) { yield(path) } end end
in_a_temporary_repo_directory(directory = "/", repo_contents_path = nil) { |path| ... }
click to toggle source
# File lib/dependabot/shared_helpers.rb, line 25 def self.in_a_temporary_repo_directory(directory = "/", repo_contents_path = nil, &block) if repo_contents_path path = Pathname.new(File.join(repo_contents_path, directory)). expand_path reset_git_repo(repo_contents_path) # Handle missing directories by creating an empty one and relying on the # file fetcher to raise a DependencyFileNotFound error FileUtils.mkdir_p(path) unless Dir.exist?(path) Dir.chdir(path) { yield(path) } else in_a_temporary_directory(directory, &block) end end
reset_git_repo(path)
click to toggle source
# File lib/dependabot/shared_helpers.rb, line 251 def self.reset_git_repo(path) Dir.chdir(path) do run_shell_command("git reset HEAD --hard") run_shell_command("git clean -fx") end end
reset_global_git_config(backup_path)
click to toggle source
# File lib/dependabot/shared_helpers.rb, line 269 def self.reset_global_git_config(backup_path) if backup_path.nil? FileUtils.rm(GIT_CONFIG_GLOBAL_PATH) return end return unless File.exist?(backup_path) FileUtils.mv(backup_path, GIT_CONFIG_GLOBAL_PATH) end
run_helper_subprocess(command:, function:, args:, env: nil, stderr_to_stdout: false, allow_unsafe_shell_command: false)
click to toggle source
rubocop:disable Metrics/MethodLength
# File lib/dependabot/shared_helpers.rb, line 73 def self.run_helper_subprocess(command:, function:, args:, env: nil, stderr_to_stdout: false, allow_unsafe_shell_command: false) start = Time.now stdin_data = JSON.dump(function: function, args: args) cmd = allow_unsafe_shell_command ? command : escape_command(command) # NOTE: For debugging native helpers in specs and dry-run: outputs the # bash command to run in the tmp directory created by # in_a_temporary_directory if ENV["DEBUG_FUNCTION"] == function puts helper_subprocess_bash_command(stdin_data: stdin_data, command: cmd, env: env) # Pause execution so we can run helpers inside the temporary directory byebug # rubocop:disable Lint/Debugger end env_cmd = [env, cmd].compact stdout, stderr, process = Open3.capture3(*env_cmd, stdin_data: stdin_data) time_taken = Time.now - start if ENV["DEBUG_HELPERS"] == "true" puts stdout puts stderr end # Some package managers output useful stuff to stderr instead of stdout so # we want to parse this, most package manager will output garbage here so # would mess up json response from stdout stdout = "#{stderr}\n#{stdout}" if stderr_to_stdout error_context = { command: command, function: function, args: args, time_taken: time_taken, stderr_output: stderr ? stderr[0..50_000] : "", # Truncate to ~100kb process_exit_value: process.to_s, process_termsig: process.termsig } response = JSON.parse(stdout) return response["result"] if process.success? raise HelperSubprocessFailed.new( message: response["error"], error_class: response["error_class"], error_context: error_context, trace: response["trace"] ) rescue JSON::ParserError raise HelperSubprocessFailed.new( message: stdout || "No output from command", error_class: "JSON::ParserError", error_context: error_context ) end
run_shell_command(command, allow_unsafe_shell_command: false)
click to toggle source
# File lib/dependabot/shared_helpers.rb, line 279 def self.run_shell_command(command, allow_unsafe_shell_command: false) start = Time.now cmd = allow_unsafe_shell_command ? command : escape_command(command) stdout, process = Open3.capture2e(cmd) time_taken = Time.now - start # Raise an error with the output from the shell session if the # command returns a non-zero status return stdout if process.success? error_context = { command: cmd, time_taken: time_taken, process_exit_value: process.to_s } raise SharedHelpers::HelperSubprocessFailed.new( message: stdout, error_context: error_context ) end
stash_global_git_config()
click to toggle source
# File lib/dependabot/shared_helpers.rb, line 258 def self.stash_global_git_config return unless File.exist?(GIT_CONFIG_GLOBAL_PATH) contents = File.read(GIT_CONFIG_GLOBAL_PATH) digest = Digest::SHA2.hexdigest(contents)[0...10] backup_path = GIT_CONFIG_GLOBAL_PATH + ".backup-#{digest}" FileUtils.mv(GIT_CONFIG_GLOBAL_PATH, backup_path) backup_path end
with_git_configured(credentials:) { || ... }
click to toggle source
# File lib/dependabot/shared_helpers.rb, line 157 def self.with_git_configured(credentials:) backup_git_config_path = stash_global_git_config configure_git_to_use_https_with_credentials(credentials) yield rescue Errno::ENOSPC => e raise Dependabot::OutOfDisk, e.message ensure reset_global_git_config(backup_git_config_path) end
Private Class Methods
helper_subprocess_bash_command(command:, stdin_data:, env:)
click to toggle source
# File lib/dependabot/shared_helpers.rb, line 301 def self.helper_subprocess_bash_command(command:, stdin_data:, env:) escaped_stdin_data = stdin_data.gsub("\"", "\\\"") env_keys = env ? env.compact.map { |k, v| "#{k}=#{v}" }.join(" ") + " " : "" "$ cd #{Dir.pwd} && echo \"#{escaped_stdin_data}\" | #{env_keys}#{command}" end