lib/omnibus/health_check.rb in omnibus-8.2.2 vs lib/omnibus/health_check.rb in omnibus-8.3.2

- old
+ new

@@ -1,7 +1,7 @@ -# Copyright 2012-2018 Chef Software, Inc. +# Copyright:: Copyright (c) Chef Software Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # @@ -64,24 +64,29 @@ # if the healthchecks pass # def run! measure("Health check time") do log.info(log_key) { "Running health on #{project.name}" } - bad_libs = case Ohai["platform"] - when "mac_os_x" - health_check_otool - when "aix" - health_check_aix - when "windows" - # TODO: objdump -p will provided a very limited check of - # explicit dependencies on windows. Most dependencies are - # implicit and hence not detected. - log.warn(log_key) { "Skipping dependency health checks on Windows." } - {} - else - health_check_ldd - end + bad_libs, good_libs = + case Ohai["platform"] + when "mac_os_x" + health_check_otool + when "aix" + health_check_aix + when "windows" + # TODO: objdump -p will provided a very limited check of + # explicit dependencies on windows. Most dependencies are + # implicit and hence not detected. + log.warn(log_key) { "Skipping dependency health checks on Windows." } + [{}, {}] + when "solaris2" + health_check_solaris + when "freebsd", "openbsd", "netbsd" + health_check_freebsd + else + health_check_linux + end unresolved = [] unreliable = [] detail = [] @@ -165,10 +170,14 @@ end raise HealthCheckFailed end + if good_libs.keys.length == 0 && !windows? + raise "Internal error: no good libraries were found" + end + conflict_map = {} conflict_map = relocation_check if relocation_checkable? if conflict_map.keys.length > 0 @@ -278,23 +287,24 @@ # the bad libraries (library_name -> dependency_name -> satisfied_lib_path -> count) # def health_check_otool current_library = nil bad_libs = {} + good_libs = {} - read_shared_libs("find #{project.install_dir}/ -type f | egrep '\.(dylib|bundle)$' | xargs otool -L") 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(bad_libs, current_library, name, linked) + bad_libs, good_libs = check_for_bad_library(bad_libs, good_libs, current_library, name, linked) end end - bad_libs + [bad_libs, good_libs] end # # Run healthchecks against aix. # @@ -302,71 +312,139 @@ # the bad libraries (library_name -> dependency_name -> satisfied_lib_path -> count) # def health_check_aix current_library = nil bad_libs = {} + good_libs = {} - read_shared_libs("find #{project.install_dir}/ -type f | xargs file | grep \"RISC System\" | awk -F: '{print $1}' | xargs -n 1 ldd") do |line| + read_shared_libs("find #{project.install_dir}/ -type f | xargs file | grep \"XCOFF\" | 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(bad_libs, current_library, name, linked) + ( bad_libs, good_libs ) = check_for_bad_library(bad_libs, good_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 - bad_libs + [bad_libs, good_libs] end # - # Run healthchecks against ldd. + # Run healthchecks on Solaris. # # @return [Hash<String, Hash<String, Hash<String, Int>>>] # the bad libraries (library_name -> dependency_name -> satisfied_lib_path -> count) # - def health_check_ldd - regexp_ends = ".*(" + IGNORED_ENDINGS.map { |e| e.gsub(/\./, '\.') }.join("|") + ")$" - regexp_patterns = IGNORED_PATTERNS.map { |e| ".*" + e.gsub(%r{/}, '\/') + ".*" }.join("|") - regexp = regexp_ends + "|" + regexp_patterns + def health_check_solaris + current_library = nil + bad_libs = {} + good_libs = {} + read_shared_libs("find #{project.install_dir}/ -type f | xargs file | grep \"ELF\" | awk -F: '{print $1}' | sed -e 's/:$//'", "xargs -n 1 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, good_libs ) = check_for_bad_library(bad_libs, good_libs, current_library, name, linked) + when /^\s+(.+) \(.+\)$/ + next + when /^\s+statically linked$/ + next + when /^\s+not a dynamic executable$/ # ignore non-executable files + else + log.warn(log_key) do + "Line did not match for #{current_library}\n#{line}" + end + end + end + + [bad_libs, good_libs] + end + + # + # Run healthchecks on FreeBSD + # + # @return [Hash<String, Hash<String, Hash<String, Int>>>] + # the bad libraries (library_name -> dependency_name -> satisfied_lib_path -> count) + # + def health_check_freebsd current_library = nil bad_libs = {} + good_libs = {} - read_shared_libs("find #{project.install_dir}/ -type f -regextype posix-extended ! -regex '#{regexp}' | xargs ldd") do |line| + read_shared_libs("find #{project.install_dir}/ -type f | xargs file | grep \"ELF\" | awk -F: '{print $1}' | sed -e 's/:$//'", "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(bad_libs, current_library, name, linked) + ( bad_libs, good_libs ) = check_for_bad_library(bad_libs, good_libs, current_library, name, linked) when /^\s+(.+) \(.+\)$/ next when /^\s+statically linked$/ next - when /^\s+libjvm.so/ + when /^\s+not a dynamic executable$/ # ignore non-executable files + else + log.warn(log_key) do + "Line did not match for #{current_library}\n#{line}" + end + end + end + + [bad_libs, good_libs] + end + + # + # Run healthchecks against ldd. + # + # @return [Hash<String, Hash<String, Hash<String, Int>>>] + # the bad libraries (library_name -> dependency_name -> satisfied_lib_path -> count) + # + def health_check_linux + current_library = nil + bad_libs = {} + good_libs = {} + + read_shared_libs("find #{project.install_dir}/ -type f | xargs file | grep \"ELF\" | awk -F: '{print $1}' | sed -e 's/:$//'", "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, good_libs ) = check_for_bad_library(bad_libs, good_libs, current_library, name, linked) + when /^\s+(.+) \(.+\)$/ next - when /^\s+libjava.so/ + when /^\s+statically linked$/ next - when /^\s+libmawt.so/ + when /^\s+libjvm.so/ # FIXME: should remove if it doesn't blow up server next + when /^\s+libjava.so/ # FIXME: should remove if it doesn't blow up server + next + when /^\s+libmawt.so/ # FIXME: should remove if it doesn't blow up server + next when /^\s+not a dynamic executable$/ # ignore non-executable files else log.warn(log_key) do "Line did not match for #{current_library}\n#{line}" end end end - bad_libs + [bad_libs, good_libs] end private # @@ -397,15 +475,45 @@ # @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 + def read_shared_libs(find_command, ldd_command, &output_proc) + # + # construct the list of files to check + # + + find_output = shellout!(find_command).stdout.lines + + find_output.reject! { |file| IGNORED_ENDINGS.any? { |ending| file.end_with?("#{ending}\n") } } + + find_output.reject! { |file| IGNORED_SUBSTRINGS.any? { |substr| file.include?(substr) } } + + if find_output.empty? + # probably the find_command is busted, it should never be empty or why are you using omnibus? + raise "Internal Error: Health Check found no lines" end + + if find_output.any? { |file| file !~ Regexp.new(project.install_dir) } + # every file in the find output should be within the install_dir + raise "Internal Error: Health Check lines not matching the install_dir" + end + + # + # feed the list of files to the "ldd" command + # + + # this command will typically fail if the last file isn't a valid lib/binary which happens often + ldd_output = shellout(ldd_command, input: find_output.join).stdout + + # + # do the output process to determine if the files are good or bad + # + + ldd_output.each_line do |line| + output_proc.call(line) + end end # # Check the given path and library for "bad" libraries. # @@ -418,11 +526,11 @@ # @param [String] # actual path of library satisfying the dependency # # @return the modified bad_library hash # - def check_for_bad_library(bad_libs, current_library, name, linked) + def check_for_bad_library(bad_libs, good_libs, current_library, name, linked) safe = nil whitelist_libs = case Ohai["platform"] when "arch" ARCH_WHITELIST_LIBS @@ -461,12 +569,13 @@ bad_libs[current_library][name][linked] += 1 else bad_libs[current_library][name][linked] = 1 end else + good_libs[current_library] = true log.debug(log_key) { " -> PASSED: #{name} is either whitelisted or safely provided." } end - bad_libs + [bad_libs, good_libs] end end end