lib/omnibus/health_check.rb in omnibus-3.1.1 vs lib/omnibus/health_check.rb in omnibus-3.2.0.rc.1
- old
+ new
@@ -15,10 +15,11 @@
#
module Omnibus
class HealthCheck
include Logging
+ include Util
WHITELIST_LIBS = [
/ld-linux/,
/libc\.so/,
/libcrypt\.so/,
@@ -32,11 +33,11 @@
/librt\.so/,
/libstdc\+\+\.so/,
/libutil\.so/,
/linux-vdso.+/,
/linux-gate\.so/,
- ]
+ ].freeze
ARCH_WHITELIST_LIBS = [
/libc\.so/,
/libcrypt\.so/,
/libdb-5\.3\.so/,
@@ -46,21 +47,21 @@
/libm\.so/,
/libnsl\.so/,
/libpthread\.so/,
/librt\.so/,
/libutil\.so/,
- ]
+ ].freeze
AIX_WHITELIST_LIBS = [
/libpthread\.a/,
/libpthreads\.a/,
/libdl.a/,
/librtl\.a/,
/libc\.a/,
/libcrypt\.a/,
/unix$/,
- ]
+ ].freeze
SOLARIS_WHITELIST_LIBS = [
/libaio\.so/,
/libavl\.so/,
/libcrypt_[di]\.so/,
@@ -88,11 +89,11 @@
/libgdbm\.so\.3/,
# solaris 9 libraries:
/libm\.so\.1/,
/libc_psr\.so\.1/,
/s9_preload\.so\.1/,
- ]
+ ].freeze
SMARTOS_WHITELIST_LIBS = [
/libm.so/,
/libpthread.so/,
/librt.so/,
@@ -104,11 +105,11 @@
/libmd.so/,
/libc.so/,
/libgcc_s.so/,
/libstdc\+\+\.so/,
/libcrypt.so/,
- ]
+ ].freeze
MAC_WHITELIST_LIBS = [
/libobjc\.A\.dylib/,
/libSystem\.B\.dylib/,
/CoreFoundation/,
@@ -122,31 +123,70 @@
/libffi\.dylib/,
/libncurses\.5\.4\.dylib/,
/libiconv/,
/libstdc\+\+\.6\.dylib/,
/libc\+\+\.1\.dylib/,
- ]
+ ].freeze
FREEBSD_WHITELIST_LIBS = [
/libc\.so/,
/libcrypt\.so/,
/libm\.so/,
/librt\.so/,
/libthr\.so/,
/libutil\.so/,
- ]
+ ].freeze
- def self.run(install_dir, whitelist_files = [])
- case Ohai.platform
- when 'mac_os_x'
- bad_libs = health_check_otool(install_dir, whitelist_files)
- when 'aix'
- bad_libs = health_check_aix(install_dir, whitelist_files)
- else
- bad_libs = health_check_ldd(install_dir, whitelist_files)
+ class << self
+ # @see (HealthCheck#new)
+ def run!(project)
+ new(project).run!
end
+ end
+ #
+ # The project to healthcheck.
+ #
+ # @return [Project]
+ #
+ attr_reader :project
+
+ #
+ # Run the healthchecks against the given project. It is assumed that the
+ # project has already been built.
+ #
+ # @param [Project] project
+ # the project to health check
+ #
+ def initialize(project)
+ @project = project
+ end
+
+ #
+ # Run the given health check. Healthcheks are skipped on Windows.
+ #
+ # @raise [HealthCheckFailed]
+ # if the health check fails
+ #
+ # @return [true]
+ # if the healthchecks pass
+ #
+ def run!
+ if Ohai['platform'] == 'windows'
+ log.warn(log_key) { 'Skipping health check on Windows' }
+ return true
+ end
+
+ bad_libs = case Ohai['platform']
+ when 'mac_os_x'
+ health_check_otool
+ when 'aix'
+ health_check_aix
+ else
+ health_check_ldd
+ end
+
unresolved = []
unreliable = []
detail = []
if bad_libs.keys.length > 0
@@ -161,35 +201,35 @@
detail << "#{name}|#{lib}|#{linked}|#{count}"
end
end
end
- log.warn(log_key) { 'Failed!' }
+ log.error(log_key) { 'Failed!' }
bad_omnibus_libs, bad_omnibus_bins = bad_libs.keys.partition { |k| k.include? 'embedded/lib' }
- log.warn(log_key) do
+ log.error(log_key) do
out = "The following libraries have unsafe or unmet dependencies:\n"
bad_omnibus_libs.each do |lib|
out << " --> #{lib}\n"
end
out
end
- log.warn(log_key) do
+ log.error(log_key) do
out = "The following binaries have unsafe or unmet dependencies:\n"
bad_omnibus_bins.each do |bin|
out << " --> #{bin}\n"
end
out
end
if unresolved.length > 0
- log.warn(log_key) do
+ log.error(log_key) do
out = "The following requirements could not be resolved:\n"
unresolved.each do |lib|
out << " --> #{lib}\n"
end
@@ -197,11 +237,11 @@
out
end
end
if unreliable.length > 0
- log.warn(log_key) do
+ log.error(log_key) do
out = "The following libraries cannot be guaranteed to be on "
out << "target systems:\n"
unreliable.each do |lib|
out << " --> #{lib}\n"
@@ -209,11 +249,11 @@
out
end
end
- log.warn(log_key) do
+ log.error(log_key) do
out = "The precise failures were:\n"
detail.each do |line|
item, dependency, location, count = line.split('|')
reason = location =~ /not found/ ? 'Unresolved dependency' : 'Unsafe dependency'
@@ -226,150 +266,87 @@
end
out
end
- raise 'Health Check Failed'
+ raise HealthCheckFailed
end
+
+ true
end
- def self.health_check_otool(install_dir, whitelist_files)
- otool_cmd = "find #{install_dir}/ -type f | egrep '\.(dylib|bundle)$' | xargs otool -L > otool.out 2>/dev/null"
- log.info(log_key) { "Executing: `#{otool_cmd}`" }
- shell = Mixlib::ShellOut.new(otool_cmd, timeout: 3600)
- shell.run_command
-
- otool_output = File.read('otool.out')
-
+ #
+ # Run healthchecks against otool.
+ #
+ # @return [Array<String>]
+ # the bad libraries
+ #
+ def health_check_otool
current_library = nil
bad_libs = {}
- otool_output.each_line do |line|
+ read_shared_libs("find #{project.install_dir}/ -type f | egrep '\.(dylib|bundle)$' | xargs otool -L") do |line|
case line
when /^(.+):$/
current_library = Regexp.last_match[1]
when /^\s+(.+) \(.+\)$/
linked = Regexp.last_match[1]
name = File.basename(linked)
- bad_libs = check_for_bad_library(install_dir, bad_libs, whitelist_files, current_library, name, linked)
+ bad_libs = check_for_bad_library(bad_libs, current_library, name, linked)
end
end
- File.delete('otool.out')
-
bad_libs
end
- def self.check_for_bad_library(install_dir, bad_libs, whitelist_files, current_library, name, linked)
- safe = nil
-
- whitelist_libs = case Ohai.platform
- when 'arch'
- ARCH_WHITELIST_LIBS
- when 'mac_os_x'
- MAC_WHITELIST_LIBS
- when 'solaris2'
- SOLARIS_WHITELIST_LIBS
- when 'smartos'
- SMARTOS_WHITELIST_LIBS
- when 'freebsd'
- FREEBSD_WHITELIST_LIBS
- when 'aix'
- AIX_WHITELIST_LIBS
- else
- WHITELIST_LIBS
- end
- whitelist_libs.each do |reg|
- safe ||= true if reg.match(name)
- end
- whitelist_files.each do |reg|
- safe ||= true if reg.match(current_library)
- end
-
- log.debug(log_key) { " --> Dependency: #{name}" }
- log.debug(log_key) { " --> Provided by: #{linked}" }
-
- if !safe && linked !~ Regexp.new(install_dir)
- log.debug(log_key) { " -> FAILED: #{current_library} has unsafe dependencies" }
- bad_libs[current_library] ||= {}
- bad_libs[current_library][name] ||= {}
- if bad_libs[current_library][name].key?(linked)
- bad_libs[current_library][name][linked] += 1
- else
- bad_libs[current_library][name][linked] = 1
- end
- else
- log.debug(log_key) { " -> PASSED: #{name} is either whitelisted or safely provided." }
- end
-
- bad_libs
- end
-
- def self.health_check_aix(install_dir, whitelist_files)
- #
- # ShellOut has GC turned off during execution, so when we're
- # executing extremely long commands with lots of output, we
- # should be mindful that the string concatentation for building
- # #stdout will hurt memory usage drastically
- #
- ldd_cmd = "find #{install_dir}/ -type f | xargs file | grep \"RISC System\" | awk -F: '{print $1}' | xargs -n 1 ldd > ldd.out 2>/dev/null"
-
- log.info(log_key) { "Executing `#{ldd_cmd}`" }
- shell = Mixlib::ShellOut.new(ldd_cmd, timeout: 3600)
- shell.run_command
-
- ldd_output = File.read('ldd.out')
-
+ #
+ # Run healthchecks against aix.
+ #
+ # @return [Array<String>]
+ # the bad libraries
+ #
+ def health_check_aix
current_library = nil
bad_libs = {}
- ldd_output.each_line do |line|
+ read_shared_libs("find #{project.install_dir}/ -type f | xargs file | grep \"RISC System\" | awk -F: '{print $1}' | xargs -n 1 ldd") do |line|
case line
when /^(.+) needs:$/
current_library = Regexp.last_match[1]
log.debug(log_key) { "Analyzing dependencies for #{current_library}" }
when /^\s+(.+)$/
name = Regexp.last_match[1]
linked = Regexp.last_match[1]
- bad_libs = check_for_bad_library(install_dir, bad_libs, whitelist_files, current_library, name, linked)
+ bad_libs = check_for_bad_library(bad_libs, current_library, name, linked)
when /File is not an executable XCOFF file/ # ignore non-executable files
else
log.warn(log_key) { "Line did not match for #{current_library}\n#{line}" }
end
end
- File.delete('ldd.out')
bad_libs
end
- def self.health_check_ldd(install_dir, whitelist_files)
- #
- # ShellOut has GC turned off during execution, so when we're
- # executing extremely long commands with lots of output, we
- # should be mindful that the string concatentation for building
- # #stdout will hurt memory usage drastically
- #
- ldd_cmd = "find #{install_dir}/ -type f | xargs ldd > ldd.out 2>/dev/null"
-
- log.info(log_key) { "Executing `#{ldd_cmd}`" }
- shell = Mixlib::ShellOut.new(ldd_cmd, timeout: 3600)
- shell.run_command
-
- ldd_output = File.read('ldd.out')
-
+ #
+ # Run healthchecks against ldd.
+ #
+ # @return [Array<String>]
+ # the bad libraries
+ #
+ def health_check_ldd
current_library = nil
bad_libs = {}
- ldd_output.each_line do |line|
+ read_shared_libs("find #{project.install_dir}/ -type f | xargs ldd") do |line|
case line
when /^(.+):$/
current_library = Regexp.last_match[1]
log.debug(log_key) { "Analyzing dependencies for #{current_library}" }
when /^\s+(.+) \=\>\s+(.+)( \(.+\))?$/
name = Regexp.last_match[1]
linked = Regexp.last_match[2]
- bad_libs = check_for_bad_library(install_dir, bad_libs, whitelist_files, current_library, name, linked)
+ bad_libs = check_for_bad_library(bad_libs, current_library, name, linked)
when /^\s+(.+) \(.+\)$/
next
when /^\s+statically linked$/
next
when /^\s+libjvm.so/
@@ -384,10 +361,88 @@
"Line did not match for #{current_library}\n#{line}"
end
end
end
- File.delete('ldd.out')
+ bad_libs
+ end
+
+ private
+
+ #
+ # The list of whitelisted (ignored) files from the project and softwares.
+ #
+ # @return [Array<String, Regexp>]
+ #
+ def whitelist_files
+ project.library.components.inject([]) do |array, component|
+ array += component.whitelist_files
+ array
+ end
+ end
+
+ #
+ # Execute the given command, yielding each line.
+ #
+ # @param [String] command
+ # the command to execute
+ # @yield [String]
+ # each line
+ #
+ def read_shared_libs(command)
+ cmd = shellout(command)
+ cmd.stdout.each_line do |line|
+ yield line
+ end
+ end
+
+ #
+ # Check the given path and library for "bad" libraries.
+ #
+ def check_for_bad_library(bad_libs, current_library, name, linked)
+ safe = nil
+
+ whitelist_libs = case Ohai['platform']
+ when 'arch'
+ ARCH_WHITELIST_LIBS
+ when 'mac_os_x'
+ MAC_WHITELIST_LIBS
+ when 'solaris2'
+ SOLARIS_WHITELIST_LIBS
+ when 'smartos'
+ SMARTOS_WHITELIST_LIBS
+ when 'freebsd'
+ FREEBSD_WHITELIST_LIBS
+ when 'aix'
+ AIX_WHITELIST_LIBS
+ else
+ WHITELIST_LIBS
+ end
+
+ whitelist_libs.each do |reg|
+ safe ||= true if reg.match(name)
+ end
+
+ whitelist_files.each do |reg|
+ safe ||= true if reg.match(current_library)
+ end
+
+ log.debug(log_key) { " --> Dependency: #{name}" }
+ log.debug(log_key) { " --> Provided by: #{linked}" }
+
+ if !safe && linked !~ Regexp.new(project.install_dir)
+ log.debug(log_key) { " -> FAILED: #{current_library} has unsafe dependencies" }
+ bad_libs[current_library] ||= {}
+ bad_libs[current_library][name] ||= {}
+ if bad_libs[current_library][name].key?(linked)
+ bad_libs[current_library][name][linked] += 1
+ else
+ bad_libs[current_library][name][linked] = 1
+ end
+ else
+ log.debug(log_key) { " -> PASSED: #{name} is either whitelisted or safely provided." }
+ end
+
bad_libs
end
end
end