Add error handling and output handling to sybase_session

Signed-off-by: Clinton Wolfe <clintoncwolfe@gmail.com>
This commit is contained in:
Clinton Wolfe 2021-06-02 22:06:58 -04:00
parent 99ae095c66
commit 7da5c4aec3

View file

@ -32,7 +32,7 @@ module Inspec::Resources
@bin = opts[:bin] || "isql" @bin = opts[:bin] || "isql"
@col_sep = "|" @col_sep = "|"
fail_resource "Can't run Sybase checks without authentication" unless (username && password) fail_resource "Can't run Sybase checks without authentication" unless username && password
fail_resource "You must provide a server name for the session" unless server fail_resource "You must provide a server name for the session" unless server
fail_resource "You must provide a database name for the session" unless database fail_resource "You must provide a database name for the session" unless database
fail_resource "Cannot find #{bin} CLI tool" unless inspec.command(bin).exist? fail_resource "Cannot find #{bin} CLI tool" unless inspec.command(bin).exist?
@ -45,19 +45,27 @@ module Inspec::Resources
# TODO: replace echos with a a train upload command if possible. # TODO: replace echos with a a train upload command if possible.
# echos are senstive to shell interpolation, such as the asterisk in SELECT * # echos are senstive to shell interpolation, such as the asterisk in SELECT *
res = inspec.command("echo #{sql} > #{sql_file_path}").exit_status # TODO: handle inspec.command("echo #{sql} > #{sql_file_path}").exit_status # TODO: handle
res = inspec.command("echo go >> #{sql_file_path}").exit_status # TODO: handle inspec.command("echo go >> #{sql_file_path}").exit_status # TODO: handle
# isql reuires that we have a matching locale set, but does not support C.UTF-8. en_US.UTF-8 is the least evil. # isql reuires that we have a matching locale set, but does not support C.UTF-8. en_US.UTF-8 is the least evil.
command = "LANG=en_US.UTF-8 SYBASE=#{sybase_home} #{bin} -s\"#{col_sep}\" -w80000 -S #{server} -U #{username} -D #{database} -P \"#{password}\" < #{sql_file_path}" command = "LANG=en_US.UTF-8 SYBASE=#{sybase_home} #{bin} -s\"#{col_sep}\" -w80000 -S #{server} -U #{username} -D #{database} -P \"#{password}\" < #{sql_file_path}"
inspec_cmd = inspec.command(command) isql_cmd = inspec.command(command)
# TODO: isql is ill-behaved, and returns 0 on error # Check for isql errors
# TODO: check sdterr for errors on 0 return res = isql_cmd.exit_status
# TODO: check stdout for error messages when stderr is empty "Msg 102, Level 15, State 181:\nServer 'SYBASE', Line 1:\nIncorrect syntax near '.'.\n" raise Inspec::Exceptions::ResourceFailed.new("isql exited with code #{res} and stderr '#{isql_cmd.stderr}', stdout '#{isql_cmd.stdout}'") unless res == 0
res = inspec_cmd.exit_status # TODO: handle # isql is ill-behaved, and returns 0 on error
res = inspec.command("rm #{sql_file_path}").exit_status # TODO: handle raise Inspec::Exceptions::ResourceFailed.new("isql exited with error '#{isql_cmd.stderr}', stdout '#{isql_cmd.stdout}'") unless isql_cmd.stderr == ""
DatabaseHelper::SQLQueryResult.new(inspec_cmd, parse_csv_result(inspec_cmd.stdout)) # check stdout for error messages when stderr is empty "Msg 102, Level 15, State 181:\nServer 'SYBASE', Line 1:\nIncorrect syntax near '.'.\n"
raise Inspec::Exceptions::ResourceFailed.new("isql exited with error #{isql_cmd.stdout}") if isql_cmd.stdout.match?(/Msg\s\d+,\sLevel\s\d+,\sState\s\d+/)
# Clean up temporary file
rm_cmd = inspec.command("rm #{sql_file_path}")
res = rm_cmd.exit_status # TODO: handle
raise Inspec::Exceptions::ResourceFailed.new("Unable to delete temproary SQL input file at #{sql_file_path}: #{rm_cmd.stderr}") unless res == 0
DatabaseHelper::SQLQueryResult.new(isql_cmd, parse_csv_result(isql_cmd.stdout))
end end
def to_s def to_s
@ -68,11 +76,12 @@ module Inspec::Resources
def parse_csv_result(stdout) def parse_csv_result(stdout)
output = stdout.sub(/\r/, "").strip output = stdout.sub(/\r/, "").strip
# TODO: remove second header row lines = output.lines
# TODO: remove trailing blank line and summary line (23 rows affected) # Remove second row (all dashes) and last two rows (blank and summary line)
trimmed_output = ([lines[0]] << lines.slice(2..-3)).join("\n")
header_converter = ->(header) { header.downcase.strip } header_converter = ->(header) { header.downcase.strip }
field_converter = ->(field) { field&.strip } field_converter = ->(field) { field&.strip }
CSV.parse(output, headers: true, header_converters: header_converter, converters: field_converter, col_sep: col_sep).map { |row| Hashie::Mash.new(row.to_h) } CSV.parse(trimmed_output, headers: true, header_converters: header_converter, converters: field_converter, col_sep: col_sep).map { |row| Hashie::Mash.new(row.to_h) }
end end
end end
end end