Module: Datadog::CI::Utils::Command
- Defined in:
- lib/datadog/ci/utils/command.rb
Overview
Provides a way to call external commands with timeout
Constant Summary collapse
- DEFAULT_TIMEOUT =
seconds
10
- BUFFER_SIZE =
1024
- OPEN_STDIN_RETRY_COUNT =
3
Class Method Summary collapse
-
.exec_command(command, stdin_data: nil, timeout: DEFAULT_TIMEOUT) ⇒ Array<String, Process::Status?>
Executes a command with optional timeout and stdin data.
- .popen_with_stdin(command, stdin_data: nil, retries_left: OPEN_STDIN_RETRY_COUNT) ⇒ Object
Class Method Details
.exec_command(command, stdin_data: nil, timeout: DEFAULT_TIMEOUT) ⇒ Array<String, Process::Status?>
Executes a command with optional timeout and stdin data
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/datadog/ci/utils/command.rb', line 27 def self.exec_command(command, stdin_data: nil, timeout: DEFAULT_TIMEOUT) output = +"" exit_value = nil timeout_reached = false begin start = Core::Utils::Time.get_time _, stderrout, thread = popen_with_stdin(command, stdin_data: stdin_data) pid = thread[:pid] # wait for output and read from stdout/stderr while (Core::Utils::Time.get_time - start) < timeout # wait for data to appear in stderrout channel # maximum wait time 100ms Kernel.select([stderrout], [], [], 0.1) begin output << stderrout.read_nonblock(1024) rescue IO::WaitReadable rescue EOFError # we're done here, we return from this cycle when we processed the whole output of the command break end end if (Core::Utils::Time.get_time - start) > timeout timeout_reached = true end if thread.alive? begin Process.kill("TERM", pid) rescue # Process already terminated end end thread.join(1) exit_value = thread.value rescue Errno::EPIPE return ["Error writing to stdin", nil] ensure stderrout&.close end # we read command's output as binary so now we need to set an appropriate encoding for the result encoding = Encoding.default_external # Sometimes Encoding.default_external is somehow set to US-ASCII which breaks # commit messages with UTF-8 characters like emojis # We force output's encoding to be UTF-8 in this case # This is safe to do as UTF-8 is compatible with US-ASCII if Encoding.default_external == Encoding::US_ASCII encoding = Encoding::UTF_8 end output.force_encoding(encoding) output.strip! # There's always a "\n" at the end of the command output if timeout_reached && output.empty? output = "Command timed out after #{timeout} seconds" end [output, exit_value] end |
.popen_with_stdin(command, stdin_data: nil, retries_left: OPEN_STDIN_RETRY_COUNT) ⇒ Object
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/datadog/ci/utils/command.rb', line 94 def self.popen_with_stdin(command, stdin_data: nil, retries_left: OPEN_STDIN_RETRY_COUNT) stdin = nil result = Open3.popen2e(*command) stdin = result.first # write input to stdin begin stdin.write(stdin_data) if stdin_data rescue Errno::EPIPE => e if retries_left > 0 return popen_with_stdin(command, stdin_data: stdin_data, retries_left: retries_left - 1) else raise e end end result ensure stdin&.close end |