lib/ansible/runner/response.rb
module Ansible
class Runner
class Response
include Vmdb::Logging
attr_reader :base_dir, :command_line, :stderr, :debug, :ident
# @return [String] Stdout that is text, where the human readable part is extracted from the JSON encoded objects
def self.parsed_stdout_to_human(parsed_stdout)
parsed_stdout.map { |l| l["stdout"] }.join("\n")
end
# Response object designed for holding full response from ansible-runner
#
# @param base_dir [String] ansible-runner private_data_dir parameter
# @param command_line [String] Command line of the ansible-runner run
# @param return_code [Integer] Return code of the ansible-runner run, 0 == ok, others mean failure
# @param stdout [String] Stdout from ansible-runner run
# @param stderr [String] Stderr from ansible-runner run
# @param ident [String] ansible-runner ident parameter
# @param debug [Boolean] whether or not to delete base_dir after run (for debugging)
def initialize(base_dir:, command_line: nil, return_code: nil, stdout: nil, stderr: nil, ident: "result", debug: false)
@base_dir = base_dir
@ident = ident
@command_line = command_line
@return_code = return_code
@stdout = stdout
@parsed_stdout = parse_stdout(stdout) if stdout
@stderr = stderr
@debug = debug
end
# @return [Integer] Return code of the ansible-runner run, 0 == ok, others mean failure
def return_code
@return_code ||= load_return_code
end
# @return [String] Stdout that is text, where each line should be JSON encoded object
def stdout
@stdout ||= load_stdout
end
# @return [String] Stdout that is text, where the human readable part is extracted from the JSON encoded objects
def human_stdout
@human_stdout ||= self.class.parsed_stdout_to_human(parsed_stdout)
end
# @return [Array<Hash>] Array of hashes as individual Ansible plays
def parsed_stdout
@parsed_stdout ||= parse_stdout(stdout)
end
# Loads needed data from the filesystem and deletes the ansible-runner base dir
def cleanup_filesystem!
# Load all needed files, before we cleanup the dir
return_code
stdout
return if debug
FileUtils.remove_entry(base_dir)
end
private
# Parses stdout to array of hashes
#
# @param stdout [String] Stdout that is text, where each line should be JSON encoded object
# @return [Array<Hash>] Array of hashes as individual Ansible plays
def parse_stdout(stdout)
parsed_stdout = []
# output is JSON per new line
stdout.each_line do |line|
# TODO(lsmola) we can remove exception handling when this is fixed
# https://github.com/ansible/ansible-runner/issues/89#issuecomment-404236832 , so it fails early if there is
# a non json line
begin
data = JSON.parse(line)
parsed_stdout << data if data.kind_of?(Hash)
rescue => e
_log.warn("Couldn't parse JSON from: #{e}")
end
end
parsed_stdout
end
# Reads a return code from a file used by ansible-runner
#
# @return [Integer] Return code of the ansible-runner run, 0 == ok, others mean failure
def load_return_code
File.read(File.join(base_dir, "artifacts", ident, "rc")).to_i
rescue
_log.warn("Couldn't find ansible-runner return code in #{base_dir}")
1
end
# @return [String] Stdout that is text, where each line should be JSON encoded object
def load_stdout
"".tap do |stdout|
# Dir.glob for all `job_events`, and sort them by the "counter"
# integer in the file name, which is the first digit(s) prior to a
# '-' in the file.
#
# job_events/1-2f97771f-c3d1-4123-8648-d035d48be4e8.json
# job_events/10-6f5dc948-c42f-4f3d-a357-151ec3e0b42e.json
# job_events/11-c0c9fdbe-8a69-4ac8-817b-19b567b514ac.json
# job_events/12-66c5f878-8fdc-4d76-9faf-2b42495a2636.json
# job_events/2-080027c4-9455-90b8-e116-000000000006.json
# job_events/3-080027c4-9455-90b8-e116-00000000000d.json
# ...
#
# And since `Dir.glob`'s sort order is operating system dependent, we
# sort manually by the basename to ensure the proper order, and the
# `File.basename` calls are done in a `.sort_by!` up front so they
# aren't triggered for each block call in a traditional `.sort!`.
#
job_event_files = Dir.glob(File.join(base_dir, "artifacts", ident, "job_events", "*.json"))
.sort_by! { |fname| fname.match(%r{job_events/(\d+)})[1].to_i }
# Read each file and added it to the `stdout` string.
#
# Also add a newline after each File read if one doesn't already
# exist (`ansible-runner` is inconsistent with it's use of new-lines
# at the end of files).
job_event_files.each do |filename|
stdout << File.read(filename)
stdout << "\n" unless stdout[-1] == "\n"
end
end
rescue
_log.warn("Couldn't find ansible-runner stdout in #{base_dir}")
""
end
end
end
end