diff --git a/lib/train.rb b/lib/train.rb index c85e90cf..6a85b7bb 100644 --- a/lib/train.rb +++ b/lib/train.rb @@ -6,7 +6,6 @@ require 'train/options' require 'train/plugins' require 'train/errors' -require 'train/platforms' require 'uri' module Train diff --git a/lib/train/errors.rb b/lib/train/errors.rb index b3fb8411..6a506952 100644 --- a/lib/train/errors.rb +++ b/lib/train/errors.rb @@ -20,7 +20,4 @@ class ClientError < ::StandardError; end # Base exception class for all exceptions that are caused by other failures # in the transport layer. class TransportError < ::StandardError; end - - # Exception for when no platform can be detected - class PlatformDetectionFailed < ::StandardError; end end diff --git a/lib/train/extras.rb b/lib/train/extras.rb index 89d5f62f..a9ed8118 100644 --- a/lib/train/extras.rb +++ b/lib/train/extras.rb @@ -4,6 +4,7 @@ module Train::Extras require 'train/extras/command_wrapper' + require 'train/extras/os_common' require 'train/extras/stat' CommandResult = Struct.new(:stdout, :stderr, :exit_status) diff --git a/lib/train/extras/linux_lsb.rb b/lib/train/extras/linux_lsb.rb new file mode 100644 index 00000000..46e73d53 --- /dev/null +++ b/lib/train/extras/linux_lsb.rb @@ -0,0 +1,60 @@ +# encoding: utf-8 +# author: Dominik Richter +# author: Christoph Hartmann +# +# This is heavily based on: +# +# OHAI https://github.com/chef/ohai +# by Adam Jacob, Chef Software Inc +# + +module Train::Extras + module LinuxLSB + def lsb_config(content) + { + id: content[/^DISTRIB_ID=["']?(.+?)["']?$/, 1], + release: content[/^DISTRIB_RELEASE=["']?(.+?)["']?$/, 1], + codename: content[/^DISTRIB_CODENAME=["']?(.+?)["']?$/, 1], + } + end + + def lsb_release + raw = @backend.run_command('lsb_release -a').stdout + { + id: raw[/^Distributor ID:\s+(.+)$/, 1], + release: raw[/^Release:\s+(.+)$/, 1], + codename: raw[/^Codename:\s+(.+)$/, 1], + } + end + + def lsb + return @lsb if defined?(@lsb) + @lsb = {} + if !(raw = get_config('/etc/lsb-release')).nil? + @lsb = lsb_config(raw) + elsif unix_file?('/usr/bin/lsb_release') + @lsb = lsb_release + end + @lsb + end + + def detect_linux_via_lsb + return false if lsb[:id].nil? + id = lsb[:id].downcase + case id + when /redhat/ + @platform[:family] = 'redhat' + when /amazon/ + @platform[:family] = 'amazon' + when /scientificsl/ + @platform[:family] = 'scientific' + when /xenserver/ + @platform[:family] = 'xenserver' + else + @platform[:family] = id + end + @platform[:release] = lsb[:release] + true + end + end +end diff --git a/lib/train/extras/os_common.rb b/lib/train/extras/os_common.rb new file mode 100644 index 00000000..bfee5e33 --- /dev/null +++ b/lib/train/extras/os_common.rb @@ -0,0 +1,151 @@ +# encoding: utf-8 +# author: Dominik Richter +# author: Christoph Hartmann +# +# This is heavily based on: +# +# OHAI https://github.com/chef/ohai +# by Adam Jacob, Chef Software Inc +# + +require 'train/extras/os_detect_darwin' +require 'train/extras/os_detect_linux' +require 'train/extras/os_detect_unix' +require 'train/extras/os_detect_windows' +require 'train/extras/os_detect_esx' +require 'train/extras/os_detect_arista_eos' +require 'train/extras/os_detect_openvms' + +module Train::Extras + class OSCommon + include Train::Extras::DetectDarwin + include Train::Extras::DetectLinux + include Train::Extras::DetectUnix + include Train::Extras::DetectWindows + include Train::Extras::DetectEsx + include Train::Extras::DetectAristaEos + include Train::Extras::DetectOpenVMS + + attr_accessor :backend + def initialize(backend, platform = nil) + @backend = backend + @platform = platform || {} + detect_family + end + + def [](key) + @platform[key] + end + + def to_hash + @platform + end + + OS = { # rubocop:disable Style/MutableConstant + 'redhat' => REDHAT_FAMILY, + 'debian' => DEBIAN_FAMILY, + 'suse' => SUSE_FAMILY, + 'fedora' => %w{fedora}, + 'bsd' => %w{ + freebsd netbsd openbsd darwin + }, + 'solaris' => %w{ + solaris smartos omnios openindiana opensolaris nexentacore + }, + 'windows' => %w{ + windows + }, + 'aix' => %w{ + aix + }, + 'hpux' => %w{ + hpux + }, + 'esx' => %w{ + esx + }, + 'darwin' => %w{ + darwin + }, + } + + OS['linux'] = %w{linux alpine arch coreos exherbo gentoo slackware fedora amazon} + OS['redhat'] + OS['debian'] + OS['suse'] + + OS['unix'] = %w{unix aix hpux qnx} + OS['linux'] + OS['solaris'] + OS['bsd'] + + # Helper methods to check the OS type + # Provides methods in the form of: linux?, unix?, solaris? ... + OS.keys.each do |os_family| + define_method((os_family + '?').to_sym) do + OS[os_family].include?(@platform[:family]) + end + end + + private + + def detect_family + # if some information is already defined, try to verify it + # with the remaining detection + unless @platform[:family].nil? + # return ok if the preconfigured family yielded a good result + return true if detect_family_type + # if not, reset the platform to presets and run the full detection + # TODO: print an error message in this case, as the instantiating + # backend is doing something wrong + @platform = {} + end + + # TODO: extend base implementation for detecting the family type + # to Windows and others + case uname_s + when /unrecognized command verb/ + @platform[:family] = 'openvms' + when /linux/i + @platform[:family] = 'linux' + when /./ + @platform[:family] = 'unix' + else + # Don't know what this is + @platform[:family] = nil + end + + # try to detect the platform if the platform is set to nil, otherwise this code will never work + return nil if @platform[:family].nil? + detect_family_type + end + + def detect_family_type # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + pf = @platform[:family] + + return detect_windows if pf == 'windows' + return detect_darwin if pf == 'darwin' + return detect_esx if pf == 'esx' + return detect_openvms if pf =='openvms' + + if %w{freebsd netbsd openbsd aix solaris2 hpux}.include?(pf) + return detect_via_uname + end + + # unix based systems combine the above + return true if pf == 'unix' and detect_darwin + return true if pf == 'unix' and detect_esx + # This is assuming that pf is set to unix, this should be if pf == 'linux' + return true if pf == 'unix' and detect_arista_eos + return true if pf == 'unix' and detect_via_uname + + # if we arrive here, we most likey have a regular linux + detect_linux + end + + def get_config(path) + res = @backend.run_command("test -f #{path} && cat #{path}") + # ignore files that can't be read + return nil if res.exit_status != 0 + res.stdout + end + + def unix_file?(path) + @backend.run_command("test -f #{path}").exit_status == 0 + end + end +end diff --git a/lib/train/extras/os_detect_arista_eos.rb b/lib/train/extras/os_detect_arista_eos.rb new file mode 100644 index 00000000..597eb3a5 --- /dev/null +++ b/lib/train/extras/os_detect_arista_eos.rb @@ -0,0 +1,34 @@ +# encoding: utf-8 +# author: Jere Julian +# +# Arista EOS has 2 modes. Most compliance tests will use the network CLI +# but when working with vagrant, its common to encounter the raw bash shell. +require 'json' + +module Train::Extras + module DetectAristaEos + def detect_arista_eos + if unix_file?('/usr/bin/FastCli') + cmd = @backend.run_command('FastCli -p 15 -c "show version | json"') + @platform[:name] = 'arista_eos_bash' + family = 'fedora' + else + cmd = @backend.run_command('show version | json') + end + + # in PTY mode, stderr is matched with stdout, therefore it may not be empty + output = cmd.stdout + if cmd.exit_status == 0 && !output.empty? + eos_ver = JSON.parse(output) + @platform[:name] = @platform[:name] || 'arista_eos' + family ||= 'arista_eos' + @platform[:family] = family + @platform[:release] = eos_ver['version'] + @platform[:arch] = eos_ver['architecture'] + true + else + false + end + end + end +end diff --git a/lib/train/extras/os_detect_darwin.rb b/lib/train/extras/os_detect_darwin.rb new file mode 100644 index 00000000..cc3287d1 --- /dev/null +++ b/lib/train/extras/os_detect_darwin.rb @@ -0,0 +1,40 @@ +# encoding: utf-8 +# author: Dominik Richter +# author: Christoph Hartmann +# +# This is heavily based on: +# +# OHAI https://github.com/chef/ohai +# by Adam Jacob, Chef Software Inc +# +require 'train/extras/uname' + +module Train::Extras + module DetectDarwin + include Train::Extras::Uname + + def detect_darwin + cmd = @backend.run_command('/usr/bin/sw_vers') + # TODO: print an error in this step of the detection, + # as it shouldnt happen + return false if cmd.exit_status != 0 + # TODO: ditto on error + return false if cmd.stdout.empty? + + name = cmd.stdout[/^ProductName:\s+(.+)$/, 1] + # TODO: ditto on error + return false if name.nil? + @platform[:name] = name.downcase.chomp.tr(' ', '_') + @platform[:release] = cmd.stdout[/^ProductVersion:\s+(.+)$/, 1] + @platform[:build] = cmd.stdout[/^BuildVersion:\s+(.+)$/, 1] + # TODO: keep for now due to backwards compatibility with serverspec + @platform[:family] = 'darwin' + detect_darwin_arch + true + end + + def detect_darwin_arch + @platform[:arch] = uname_m + end + end +end diff --git a/lib/train/extras/os_detect_esx.rb b/lib/train/extras/os_detect_esx.rb new file mode 100644 index 00000000..b05ab1f8 --- /dev/null +++ b/lib/train/extras/os_detect_esx.rb @@ -0,0 +1,22 @@ +# encoding: utf-8 +# author: Dominik Richter +# author: Christoph Hartmann +# +# This is heavily based on: +# +# OHAI https://github.com/chef/ohai +# by Adam Jacob, Chef Software Inc +# + +module Train::Extras + module DetectEsx + def detect_esx + if uname_s.downcase.chomp == 'vmkernel' + @platform[:family] = 'esx' + @platform[:name] = uname_s.lines[0].chomp + @platform[:release] = uname_r.lines[0].chomp + true + end + end + end +end diff --git a/lib/train/extras/os_detect_linux.rb b/lib/train/extras/os_detect_linux.rb new file mode 100644 index 00000000..e9f36585 --- /dev/null +++ b/lib/train/extras/os_detect_linux.rb @@ -0,0 +1,164 @@ +# encoding: utf-8 +# author: Dominik Richter +# author: Christoph Hartmann +# +# This is heavily based on: +# +# OHAI https://github.com/chef/ohai +# by Adam Jacob, Chef Software Inc +# + +require 'train/extras/linux_lsb' +require 'train/extras/uname' + +module Train::Extras + module DetectLinux # rubocop:disable Metrics/ModuleLength + DEBIAN_FAMILY = %w{debian ubuntu linuxmint raspbian}.freeze + REDHAT_FAMILY = %w{centos redhat oracle scientific enterpriseenterprise xenserver cloudlinux ibm_powerkvm nexus_centos wrlinux virtuozzo parallels}.freeze + SUSE_FAMILY = %w{suse opensuse}.freeze + + include Train::Extras::LinuxLSB + include Train::Extras::Uname + + def detect_linux_via_config # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity + if !(raw = get_config('/etc/oracle-release')).nil? + @platform[:name] = 'oracle' + @platform[:release] = redhatish_version(raw) + elsif !(raw = get_config('/etc/enterprise-release')).nil? + @platform[:name] = 'oracle' + @platform[:release] = redhatish_version(raw) + elsif !(raw = get_config('/etc/debian_version')).nil? + case lsb[:id] + when /ubuntu/i + @platform[:name] = 'ubuntu' + @platform[:release] = lsb[:release] + when /linuxmint/i + @platform[:name] = 'linuxmint' + @platform[:release] = lsb[:release] + else + @platform[:name] = unix_file?('/usr/bin/raspi-config') ? 'raspbian' : 'debian' + @platform[:release] = raw.chomp + end + elsif !(raw = get_config('/etc/parallels-release')).nil? + @platform[:name] = redhatish_platform(raw) + @platform[:release] = raw[/(\d\.\d\.\d)/, 1] + elsif !(raw = get_config('/etc/redhat-release')).nil? + # TODO: Cisco + # TODO: fully investigate os-release and integrate it; + # here we just use it for centos + @platform[:name] = if !(osrel = get_config('/etc/os-release')).nil? && osrel =~ /centos/i + 'centos' + else + redhatish_platform(raw) + end + + @platform[:release] = redhatish_version(raw) + elsif !(raw = get_config('/etc/system-release')).nil? + # Amazon Linux + @platform[:name] = redhatish_platform(raw) + @platform[:release] = redhatish_version(raw) + elsif !(suse = get_config('/etc/SuSE-release')).nil? + version = suse.scan(/VERSION = (\d+)\nPATCHLEVEL = (\d+)/).flatten.join('.') + version = suse[/VERSION = ([\d\.]{2,})/, 1] if version == '' + @platform[:release] = version + @platform[:name] = if suse =~ /^openSUSE/ + 'opensuse' + else + 'suse' + end + elsif !(raw = get_config('/etc/arch-release')).nil? + @platform[:name] = 'arch' + # Because this is a rolling release distribution, + # use the kernel release, ex. 4.1.6-1-ARCH + @platform[:release] = uname_r + elsif !(raw = get_config('/etc/slackware-version')).nil? + @platform[:name] = 'slackware' + @platform[:release] = raw.scan(/(\d+|\.+)/).join + elsif !(raw = get_config('/etc/exherbo-release')).nil? + @platform[:name] = 'exherbo' + # Because this is a rolling release distribution, + # use the kernel release, ex. 4.1.6 + @platform[:release] = uname_r + elsif !(raw = get_config('/etc/gentoo-release')).nil? + @platform[:name] = 'gentoo' + @platform[:release] = raw.scan(/(\d+|\.+)/).join + elsif !(raw = get_config('/etc/alpine-release')).nil? + @platform[:name] = 'alpine' + @platform[:release] = raw.strip + elsif !get_config('/etc/coreos/update.conf').nil? + @platform[:name] = 'coreos' + @platform[:release] = lsb[:release] + elsif !(os_info = fetch_os_release).nil? + if os_info['ID_LIKE'] =~ /wrlinux/ + @platform[:name] = 'wrlinux' + @platform[:release] = os_info['VERSION'] + end + end + + @platform[:family] = family_for_platform + + !@platform[:family].nil? && !@platform[:release].nil? + end + + def family_for_platform + if DEBIAN_FAMILY.include?(@platform[:name]) + 'debian' + elsif REDHAT_FAMILY.include?(@platform[:name]) + 'redhat' + elsif SUSE_FAMILY.include?(@platform[:name]) + 'suse' + else + @platform[:name] || @platform[:family] + end + end + + def redhatish_platform(conf) + conf[/^red hat/i] ? 'redhat' : conf[/(\w+)/i, 1].downcase + end + + def redhatish_version(conf) + return conf[/((\d+) \(Rawhide\))/i, 1].downcase if conf[/rawhide/i] + return conf[/Linux ((\d+|\.)+)/i, 1] if conf[/derived from .*linux/i] + conf[/release ([\d\.]+)/, 1] + end + + def detect_linux_arch + @platform[:arch] = uname_m + end + + def detect_linux + # TODO: print an error in this step of the detection + return false if uname_s.nil? || uname_s.empty? + return false if uname_r.nil? || uname_r.empty? + + detect_linux_arch + return true if detect_linux_via_config + return true if detect_linux_via_lsb + # in all other cases we failed the detection + @platform[:family] = 'unknown' + end + + def fetch_os_release + data = get_config('/etc/os-release') + return if data.nil? + + os_info = parse_os_release_info(data) + cisco_info_file = os_info['CISCO_RELEASE_INFO'] + if cisco_info_file + os_info.merge!(parse_os_release_info(get_config(cisco_info_file))) + end + + os_info + end + + def parse_os_release_info(raw) + return {} if raw.nil? + + raw.lines.each_with_object({}) do |line, memo| + line.strip! + key, value = line.split('=', 2) + memo[key] = value.gsub(/\A"|"\Z/, '') unless value.empty? + end + end + end +end diff --git a/lib/train/extras/os_detect_openvms.rb b/lib/train/extras/os_detect_openvms.rb new file mode 100644 index 00000000..18f863d3 --- /dev/null +++ b/lib/train/extras/os_detect_openvms.rb @@ -0,0 +1,29 @@ +# encoding: utf-8 +# author: Brian Doody (HPE) +# This is heavily based on: +# +# OHAI https://github.com/chef/ohai +# by Adam Jacob, Chef Software Inc +# +require 'train/extras/uname' + +module Train::Extras + module DetectOpenVMS + include Train::Extras::Uname + + def detect_openvms + cmd = @backend.run_command('show system/noprocess') + + return false if cmd.exit_status != 0 + return false if cmd.stdout.empty? + + @platform[:name] = cmd.stdout.downcase.split(' ')[0] + cmd = @backend.run_command('write sys$output f$getsyi("VERSION")') + @platform[:release] = cmd.stdout.downcase.split("\n")[1][1..-1] + cmd = @backend.run_command('write sys$output f$getsyi("ARCH_NAME")') + @platform[:arch] = cmd.stdout.downcase.split("\n")[1] + + true + end + end +end diff --git a/lib/train/extras/os_detect_unix.rb b/lib/train/extras/os_detect_unix.rb new file mode 100644 index 00000000..79d3855d --- /dev/null +++ b/lib/train/extras/os_detect_unix.rb @@ -0,0 +1,106 @@ +# encoding: utf-8 +# author: Dominik Richter +# author: Christoph Hartmann +# +# This is heavily based on: +# +# OHAI https://github.com/chef/ohai +# by Adam Jacob, Chef Software Inc +# + +module Train::Extras + module DetectUnix + def detect_via_uname # rubocop:disable Metrics/AbcSize + case uname_s.downcase + when /aix/ + @platform[:family] = 'aix' + @platform[:name] = uname_s.lines[0].chomp + out = @backend.run_command('uname -rvp').stdout + m = out.match(/(\d+)\s+(\d+)\s+(.*)/) + unless m.nil? + @platform[:release] = "#{m[2]}.#{m[1]}" + @platform[:arch] = m[3].to_s + end + when /hp-ux/ + @platform[:family] = 'hpux' + @platform[:name] = uname_s.lines[0].chomp + @platform[:release] = uname_r.lines[0].chomp + + when /freebsd/ + @platform[:family] = 'freebsd' + @platform[:name] = uname_s.lines[0].chomp + @platform[:release] = uname_r.lines[0].chomp + + when /netbsd/ + @platform[:family] = 'netbsd' + @platform[:name] = uname_s.lines[0].chomp + @platform[:release] = uname_r.lines[0].chomp + + when /openbsd/ + @platform[:family] = 'openbsd' + @platform[:name] = uname_s.lines[0].chomp + @platform[:release] = uname_r.lines[0].chomp + + when /qnx/ + @platform[:family] = 'qnx' + @platform[:name] = uname_s.lines[0].chomp.downcase + @platform[:release] = uname_r.lines[0].chomp + @platform[:arch] = uname_m + + when /sunos/ + detect_solaris + else + # in all other cases we didn't detect it + return false + end + # when we get here the detection returned a result + true + end + + def detect_solaris # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity + # read specific os name + # DEPRECATED: os[:family] is going to be deprecated, use os.solaris? + rel = get_config('/etc/release') + if /^.*(SmartOS).*$/ =~ rel + @platform[:name] = 'smartos' + @platform[:family] = 'smartos' + elsif !(m = /^\s*(OmniOS).*r(\d+).*$/.match(rel)).nil? + @platform[:name] = 'omnios' + @platform[:family] = 'omnios' + @platform[:release] = m[2] + elsif !(m = /^\s*(OpenIndiana).*oi_(\d+).*$/.match(rel)).nil? + @platform[:name] = 'openindiana' + @platform[:family] = 'openindiana' + @platform[:release] = m[2] + elsif /^\s*(OpenSolaris).*snv_(\d+).*$/ =~ rel + @platform[:name] = 'opensolaris' + @platform[:family] = 'opensolaris' + @platform[:release] = m[2] + elsif !(m = /Oracle Solaris (\d+)/.match(rel)).nil? + # TODO: should be string! + @platform[:release] = m[1] + @platform[:name] = 'solaris' + @platform[:family] = 'solaris' + elsif /^\s*(Solaris)\s.*$/ =~ rel + @platform[:name] = 'solaris' + @platform[:family] = 'solaris' + elsif /^\s*(NexentaCore)\s.*$/ =~ rel + @platform[:name] = 'nexentacore' + @platform[:family] = 'nexentacore' + else + # unknown solaris + @platform[:name] = 'solaris_distro' + @platform[:family] = 'solaris' + end + + # read release version + unless (version = /^5\.(?\d+)$/.match(uname_r)).nil? + @platform[:release] = version['release'] + end + + # read architecture + arch = @backend.run_command('uname -p') + @platform[:arch] = arch.stdout.chomp if arch.exit_status == 0 + end + end +end diff --git a/lib/train/platforms/detect/helpers/os_windows.rb b/lib/train/extras/os_detect_windows.rb similarity index 92% rename from lib/train/platforms/detect/helpers/os_windows.rb rename to lib/train/extras/os_detect_windows.rb index 4b21ce26..06d47a67 100644 --- a/lib/train/platforms/detect/helpers/os_windows.rb +++ b/lib/train/extras/os_detect_windows.rb @@ -1,7 +1,14 @@ # encoding: utf-8 - -module Train::Platforms::Detect::Helpers - module Windows +# author: Dominik Richter +# author: Christoph Hartmann +# +# This is heavily based on: +# +# OHAI https://github.com/chef/ohai +# by Adam Jacob, Chef Software Inc +# +module Train::Extras + module DetectWindows def detect_windows res = @backend.run_command('cmd /c ver') return false if res.exit_status != 0 or res.stdout.empty? diff --git a/lib/train/extras/uname.rb b/lib/train/extras/uname.rb new file mode 100644 index 00000000..4b45abe5 --- /dev/null +++ b/lib/train/extras/uname.rb @@ -0,0 +1,28 @@ +# encoding: utf-8 +# author: Dominik Richter +# author: Christoph Hartmann +# +# This is heavily based on: +# +# OHAI https://github.com/chef/ohai +# by Adam Jacob, Chef Software Inc +# +module Train::Extras + module Uname + def uname_s + @uname_s ||= backend.run_command('uname -s').stdout + end + + def uname_r + @uname_r ||= begin + res = backend.run_command('uname -r').stdout + res.strip! unless res.nil? + res + end + end + + def uname_m + @uname_m ||= backend.run_command('uname -m').stdout.chomp + end + end +end diff --git a/lib/train/platforms.rb b/lib/train/platforms.rb deleted file mode 100644 index 403181e3..00000000 --- a/lib/train/platforms.rb +++ /dev/null @@ -1,82 +0,0 @@ -# encoding: utf-8 - -require 'train/platforms/common' -require 'train/platforms/family' -require 'train/platforms/platform' -require 'train/platforms/detect' -require 'train/platforms/detect/scanner' -require 'train/platforms/detect/specifications/os' - -module Train::Platforms - class << self - # Retrieve the current platform list - # - # @return [Hash] map with platform names and their objects - def list - @list ||= {} - end - - # Retrieve the current family list - # - # @return [Hash] map with family names and their objects - def families - @families ||= {} - end - end - - # Create or update a platform - # - # @return Train::Platform - def self.name(name, condition = {}) - # Check the list to see if one is already created - plat = list[name] - unless plat.nil? - # Pass the condition incase we are adding a family relationship - plat.condition = condition unless condition.nil? - return plat - end - - Train::Platforms::Platform.new(name, condition) - end - - # Create or update a family - # - # @return Train::Platforms::Family - def self.family(name, condition = {}) - # Check the families to see if one is already created - family = families[name] - unless family.nil? - # Pass the condition incase we are adding a family relationship - family.condition = condition unless condition.nil? - return family - end - - Train::Platforms::Family.new(name, condition) - end - - # Find the families or top level platforms - # - # @return [Hash] with top level family and platforms - def self.top_platforms - top_platforms = list.select { |_key, value| value.families.empty? } - top_platforms.merge!(families.select { |_key, value| value.families.empty? }) - top_platforms - end - - # List all platforms and families in a readable output - def self.list_all - top_platforms = self.top_platforms - top_platforms.each_value do |platform| - puts platform.title - print_children(platform) if defined?(platform.children) - end - end - - def self.print_children(parent, pad = 2) - parent.children.each do |key, value| - obj = key - puts "#{' ' * pad}-> #{obj.title}#{value unless value.empty?}" - print_children(obj, pad + 2) if defined?(obj.children) && !obj.children.nil? - end - end -end diff --git a/lib/train/platforms/common.rb b/lib/train/platforms/common.rb deleted file mode 100644 index 44326c68..00000000 --- a/lib/train/platforms/common.rb +++ /dev/null @@ -1,34 +0,0 @@ -# encoding: utf-8 - -module Train::Platforms - module Common - # Add a family connection. This will create a family - # if it does not exist and add a child relationship. - def in_family(family) - if self.class == Train::Platforms::Family && @name == family - fail "Unable to add family #{@name} to itself: '#{@name}.in_family(#{family})'" - end - - # add family to the family list - family = Train::Platforms.family(family) - family.children[self] = @condition - - @families[family] = @condition - @condition = nil - self - end - - def detect(&block) - if block_given? - @detect = block - self - elsif @detect.nil? - # we are returning a block that just returns false here - # to skip the family/platform evaluation if detect is not set - ->(_) { false } - else - @detect - end - end - end -end diff --git a/lib/train/platforms/detect.rb b/lib/train/platforms/detect.rb deleted file mode 100644 index 5d0031e4..00000000 --- a/lib/train/platforms/detect.rb +++ /dev/null @@ -1,12 +0,0 @@ -# encoding: utf-8 - -module Train::Platforms - module Detect - # Main detect method to scan all platforms for a match - # - # @return Train::Platform instance or error if none found - def self.scan(backend) - Scanner.new(backend).scan - end - end -end diff --git a/lib/train/platforms/detect/helpers/os_common.rb b/lib/train/platforms/detect/helpers/os_common.rb deleted file mode 100644 index 997fb9c7..00000000 --- a/lib/train/platforms/detect/helpers/os_common.rb +++ /dev/null @@ -1,56 +0,0 @@ -# encoding: utf-8 - -require 'train/platforms/detect/helpers/os_linux' -require 'train/platforms/detect/helpers/os_windows' -require 'rbconfig' - -module Train::Platforms::Detect::Helpers - module OSCommon - include Train::Platforms::Detect::Helpers::Linux - include Train::Platforms::Detect::Helpers::Windows - - def ruby_host_os(regex) - ::RbConfig::CONFIG['host_os'] =~ regex - end - - def winrm? - Object.const_defined?('Train::Transports::WinRM::Connection') && - @backend.class == Train::Transports::WinRM::Connection - end - - def unix_file_contents(path) - # keep a log of files incase multiple checks call the same one - return @files[path] if @files.key?(path) - - res = @backend.run_command("test -f #{path} && cat #{path}") - # ignore files that can't be read - @files[path] = res.exit_status.zero? ? res.stdout : nil - @files[path] - end - - def unix_file_exist?(path) - @backend.run_command("test -f #{path}").exit_status.zero? - end - - def command_output(cmd) - res = @backend.run_command(cmd).stdout - res.strip! unless res.nil? - res - end - - def unix_uname_s - return @uname[:s] if @uname.key?(:s) - @uname[:s] = command_output('uname -s') - end - - def unix_uname_r - return @uname[:r] if @uname.key?(:r) - @uname[:r] = command_output('uname -r') - end - - def unix_uname_m - return @uname[:m] if @uname.key?(:m) - @uname[:m] = command_output('uname -m') - end - end -end diff --git a/lib/train/platforms/detect/helpers/os_linux.rb b/lib/train/platforms/detect/helpers/os_linux.rb deleted file mode 100644 index 4bfe3f5b..00000000 --- a/lib/train/platforms/detect/helpers/os_linux.rb +++ /dev/null @@ -1,75 +0,0 @@ -# encoding: utf-8 - -module Train::Platforms::Detect::Helpers - module Linux - def redhatish_platform(conf) - conf =~ /^red hat/i ? 'redhat' : /(\w+)/i.match(conf)[1].downcase - end - - def redhatish_version(conf) - case conf - when /rawhide/i - /((\d+) \(Rawhide\))/i.match(conf)[1].downcase - when /derived from .*linux/i - /Linux ((\d+|\.)+)/i.match(conf)[1] - else - /release ([\d\.]+)/.match(conf)[1] - end - end - - def linux_os_release - data = unix_file_contents('/etc/os-release') - return if data.nil? - - os_info = parse_os_release_info(data) - cisco_info_file = os_info['CISCO_RELEASE_INFO'] - if cisco_info_file - os_info.merge!(parse_os_release_info(unix_file_contents(cisco_info_file))) - end - - os_info - end - - def parse_os_release_info(raw) - return {} if raw.nil? - - raw.lines.each_with_object({}) do |line, memo| - line.strip! - next if line.empty? - key, value = line.split('=', 2) - memo[key] = value.gsub(/\A"|"\Z/, '') unless value.empty? - end - end - - def lsb_config(content) - id = /^DISTRIB_ID=["']?(.+?)["']?$/.match(content) - release = /^DISTRIB_RELEASE=["']?(.+?)["']?$/.match(content) - codename = /^DISTRIB_CODENAME=["']?(.+?)["']?$/.match(content) - { - id: id.nil? ? nil : id[1], - release: release.nil? ? nil : release[1], - codename: codename.nil? ? nil : codename[1], - } - end - - def lsb_release(content) - id = /^Distributor ID:\s+(.+)$/.match(content) - release = /^Release:\s+(.+)$/.match(content) - codename = /^Codename:\s+(.+)$/.match(content) - { - id: id.nil? ? nil : id[1], - release: release.nil? ? nil : release[1], - codename: codename.nil? ? nil : codename[1], - } - end - - def read_linux_lsb - return @lsb unless @lsb.empty? - if !(raw = unix_file_contents('/etc/lsb-release')).nil? - @lsb = lsb_config(raw) - elsif !(raw = unix_file_contents('/usr/bin/lsb-release')).nil? - @lsb = lsb_release(raw) - end - end - end -end diff --git a/lib/train/platforms/detect/scanner.rb b/lib/train/platforms/detect/scanner.rb deleted file mode 100644 index f5eb1abb..00000000 --- a/lib/train/platforms/detect/scanner.rb +++ /dev/null @@ -1,84 +0,0 @@ -# encoding: utf-8 - -require 'train/platforms/detect/helpers/os_common' - -module Train::Platforms::Detect - class Scanner - include Train::Platforms::Detect::Helpers::OSCommon - - def initialize(backend) - @backend = backend - @platform = {} - @family_hierarchy = [] - - # detect cache variables - @files = {} - @uname = {} - @lsb = {} - end - - # Main detect method to scan all platforms for a match - # - # @return Train::Platform instance or error if none found - def scan - # start with the platform/families who have no families (the top levels) - top = Train::Platforms.top_platforms - top.each do |_name, plat| - # we are doing a instance_eval here to make sure we have the proper - # context with all the detect helper methods - next unless instance_eval(&plat.detect) == true - - # if we have a match start looking at the children - plat_result = scan_children(plat) - next if plat_result.nil? - - # return platform to backend - @family_hierarchy << plat.name - return get_platform(plat_result) - end - - fail Train::PlatformDetectionFailed, 'Sorry, we are unable to detect your platform' - end - - def scan_children(parent) - parent.children.each do |plat, condition| - next unless instance_eval(&plat.detect) == true - - if plat.class == Train::Platforms::Platform - @platform[:family] = parent.name - return plat if condition.empty? || check_condition(condition) - elsif plat.class == Train::Platforms::Family - plat = scan_family_children(plat) - return plat unless plat.nil? - end - end - - nil - end - - def scan_family_children(plat) - child_result = scan_children(plat) unless plat.children.nil? - return if child_result.nil? - @family_hierarchy << plat.name - child_result - end - - def check_condition(condition) - condition.each do |k, v| - op, expected = v.strip.split(' ') - op = '==' if op == '=' - return false if @platform[k].nil? || !instance_eval("'#{@platform[k]}' #{op} '#{expected}'") - end - - true - end - - def get_platform(plat) - plat.backend = @backend - plat.platform = @platform - plat.add_platform_methods - plat.family_hierarchy = @family_hierarchy - plat - end - end -end diff --git a/lib/train/platforms/detect/specifications/os.rb b/lib/train/platforms/detect/specifications/os.rb deleted file mode 100644 index bd6c0d6c..00000000 --- a/lib/train/platforms/detect/specifications/os.rb +++ /dev/null @@ -1,465 +0,0 @@ -# encoding: utf-8 - -# rubocop:disable Style/Next -# rubocop:disable Metrics/AbcSize -# rubocop:disable Metrics/CyclomaticComplexity -# rubocop:disable Metrics/ClassLength -# rubocop:disable Metrics/MethodLength -# rubocop:disable Metrics/PerceivedComplexity - -module Train::Platforms::Detect::Specifications - class OS - def self.load - plat = Train::Platforms - - plat.family('windows') - .detect { - if winrm? || (@backend.local? && ruby_host_os(/mswin|mingw32|windows/)) - true - end - } - # windows platform - plat.name('windows').in_family('windows') - .detect { - true if detect_windows == true - } - - # unix master family - plat.family('unix') - .detect { - if unix_uname_s =~ /./ - @platform[:arch] = unix_uname_m - true - end - } - - # arista_eos family - # this has to be before redhat as EOS is based off fedora - plat.family('arista_eos').title('Arista EOS Family').in_family('unix') - .detect { - # we need a better way to determin this family - # for now we are going to just try each platform - true - } - plat.name('arista_eos').title('Arista EOS').in_family('arista_eos') - .detect { - cmd = @backend.run_command('show version | json') - if cmd.exit_status == 0 && !cmd.stdout.empty? - require 'json' - eos_ver = JSON.parse(cmd.stdout) - @platform[:release] = eos_ver['version'] - @platform[:arch] = eos_ver['architecture'] - true - end - } - plat.name('arista_eos_bash').title('Arista EOS Bash Shell').in_family('arista_eos') - .detect { - if unix_file_exist?('/usr/bin/FastCli') - cmd = @backend.run_command('FastCli -p 15 -c "show version | json"') - if cmd.exit_status == 0 && !cmd.stdout.empty? - require 'json' - eos_ver = JSON.parse(cmd.stdout) - @platform[:release] = eos_ver['version'] - @platform[:arch] = eos_ver['architecture'] - true - end - end - } - - # linux master family - plat.family('linux').in_family('unix') - .detect { - true if unix_uname_s =~ /linux/i - } - - # debian family - plat.family('debian').in_family('linux') - .detect { - true unless unix_file_contents('/etc/debian_version').nil? - } - plat.name('ubuntu').title('Ubuntu Linux').in_family('debian') - .detect { - lsb = read_linux_lsb - if lsb && lsb[:id] =~ /ubuntu/i - @platform[:release] = lsb[:release] - true - end - } - plat.name('linuxmint').title('LinuxMint').in_family('debian') - .detect { - lsb = read_linux_lsb - if lsb && lsb[:id] =~ /linuxmint/i - @platform[:release] = lsb[:release] - true - end - } - plat.name('raspbian').title('Raspbian Linux').in_family('debian') - .detect { - if (linux_os_release && linux_os_release['NAME'] =~ /raspbian/i) || \ - unix_file_exist?('/usr/bin/raspi-config') - @platform[:release] = unix_file_contents('/etc/debian_version').chomp - true - end - } - plat.name('debian').title('Debian Linux').in_family('debian') - .detect { - lsb = read_linux_lsb - if lsb && lsb[:id] =~ /debian/i - @platform[:release] = lsb[:release] - true - end - - # if we get this far we have to be some type of debian - true - } - - # fedora family - plat.family('fedora').in_family('linux') - .detect { - true if linux_os_release && linux_os_release['NAME'] =~ /fedora/i - } - plat.name('fedora').title('Fedora').in_family('fedora') - .detect { - @platform[:release] = linux_os_release['VERSION_ID'] - true - } - - # redhat family - plat.family('redhat').in_family('linux') - .detect { - # I am not sure this returns true for all redhats in this family - # for now we are going to just try each platform - # return true unless unix_file_contents('/etc/redhat-release').nil? - - true - } - plat.name('centos').title('Centos Linux').in_family('redhat') - .detect { - lsb = read_linux_lsb - if lsb && lsb[:id] =~ /centos/i - @platform[:release] = lsb[:release] - true - elsif linux_os_release && linux_os_release['NAME'] =~ /centos/i - @platform[:release] = redhatish_version(unix_file_contents('/etc/redhat-release')) - true - end - } - plat.name('oracle').title('Oracle Linux').in_family('redhat') - .detect { - if !(raw = unix_file_contents('/etc/oracle-release')).nil? - @platform[:release] = redhatish_version(raw) - true - elsif !(raw = unix_file_contents('/etc/enterprise-release')).nil? - @platform[:release] = redhatish_version(raw) - true - end - } - plat.name('scientific').title('Scientific Linux').in_family('redhat') - .detect { - lsb = read_linux_lsb - if lsb && lsb[:id] =~ /scientificsl/i - @platform[:release] = lsb[:release] - true - end - } - plat.name('xenserver').title('Xenserer Linux').in_family('redhat') - .detect { - lsb = read_linux_lsb - if lsb && lsb[:id] =~ /xenserver/i - @platform[:release] = lsb[:release] - true - end - } - plat.name('parallels-release').title('Parallels Linux').in_family('redhat') - .detect { - if !(raw = unix_file_contents('/etc/parallels-release')).nil? - @platform[:name] = redhatish_platform(raw) - @platform[:release] = raw[/(\d\.\d\.\d)/, 1] - true - end - } - plat.name('wrlinux').title('Wind River Linux').in_family('redhat') - .detect { - if linux_os_release && linux_os_release['ID_LIKE'] =~ /wrlinux/i - @platform[:release] = linux_os_release['VERSION'] - true - end - } - plat.name('amazon').title('Amazon Linux').in_family('redhat') - .detect { - lsb = read_linux_lsb - if lsb && lsb[:id] =~ /amazon/i - @platform[:release] = lsb[:release] - true - elsif (raw = unix_file_contents('/etc/system-release')) =~ /amazon/i - @platform[:name] = redhatish_platform(raw) - @platform[:release] = redhatish_version(raw) - true - end - } - # keep redhat at the end as a fallback for anything with a redhat-release - plat.name('redhat').title('Red Hat Linux').in_family('redhat') - .detect { - lsb = read_linux_lsb - if lsb && lsb[:id] =~ /redhat/i - @platform[:release] = lsb[:release] - true - elsif !(raw = unix_file_contents('/etc/redhat-release')).nil? - # must be some type of redhat - @platform[:name] = redhatish_platform(raw) - @platform[:release] = redhatish_version(raw) - true - end - } - - # suse family - plat.family('suse').in_family('linux') - .detect { - if !(suse = unix_file_contents('/etc/SuSE-release')).nil? - version = suse.scan(/VERSION = (\d+)\nPATCHLEVEL = (\d+)/).flatten.join('.') - version = suse[/VERSION = ([\d\.]{2,})/, 1] if version == '' - @platform[:release] = version - true - end - } - plat.name('opensuse').title('OpenSUSE Linux').in_family('suse') - .detect { - true if unix_file_contents('/etc/SuSE-release') =~ /^opensuse/i - } - plat.name('suse').title('Suse Linux').in_family('suse') - .detect { - true if unix_file_contents('/etc/SuSE-release') =~ /suse/i - } - - # arch - plat.name('arch').title('Arch Linux').in_family('linux') - .detect { - if !unix_file_contents('/etc/arch-release').nil? - # Because this is a rolling release distribution, - # use the kernel release, ex. 4.1.6-1-ARCH - @platform[:release] = unix_uname_r - true - end - } - - # slackware - plat.name('slackware').title('Slackware Linux').in_family('linux') - .detect { - if !(raw = unix_file_contents('/etc/slackware-version')).nil? - @platform[:release] = raw.scan(/(\d+|\.+)/).join - true - end - } - - # gentoo - plat.name('gentoo').title('Gentoo Linux').in_family('linux') - .detect { - if !(raw = unix_file_contents('/etc/gentoo-release')).nil? - @platform[:release] = raw.scan(/(\d+|\.+)/).join - true - end - } - - # exherbo - plat.name('exherbo').title('Exherbo Linux').in_family('linux') - .detect { - unless unix_file_contents('/etc/exherbo-release').nil? - # Because this is a rolling release distribution, - # use the kernel release, ex. 4.1.6 - @platform[:release] = unix_uname_r - true - end - } - - # alpine - plat.name('alpine').title('Alpine Linux').in_family('linux') - .detect { - if !(raw = unix_file_contents('/etc/alpine-release')).nil? - @platform[:release] = raw.strip - true - end - } - - # coreos - plat.name('coreos').title('CoreOS Linux').in_family('linux') - .detect { - unless unix_file_contents('/etc/coreos/update.conf').nil? - lsb = read_linux_lsb - @platform[:release] = lsb[:release] - true - end - } - - # genaric linux - # this should always be last in the linux family list - plat.name('linux').title('Genaric Linux').in_family('linux') - .detect { - true - } - - # openvms - plat.name('openvms').title('OpenVMS').in_family('unix') - .detect { - if unix_uname_s =~ /unrecognized command verb/i - cmd = @backend.run_command('show system/noprocess') - unless cmd.exit_status != 0 || cmd.stdout.empty? - @platform[:name] = cmd.stdout.downcase.split(' ')[0] - cmd = @backend.run_command('write sys$output f$getsyi("VERSION")') - @platform[:release] = cmd.stdout.downcase.split("\n")[1][1..-1] - cmd = @backend.run_command('write sys$output f$getsyi("ARCH_NAME")') - @platform[:arch] = cmd.stdout.downcase.split("\n")[1] - true - end - end - } - - # esx - plat.family('esx').title('ESXi Family') - .detect { - true if unix_uname_s =~ /vmkernel/i - } - plat.name('vmkernel').in_family('esx') - .detect { - @platform[:name] = unix_uname_s.lines[0].chomp - @platform[:release] = unix_uname_r.lines[0].chomp - true - } - - # aix - plat.family('aix').in_family('unix') - .detect { - true if unix_uname_s =~ /aix/i - } - plat.name('aix').title('Aix').in_family('aix') - .detect { - out = @backend.run_command('uname -rvp').stdout - m = out.match(/(\d+)\s+(\d+)\s+(.*)/) - unless m.nil? - @platform[:release] = "#{m[2]}.#{m[1]}" - @platform[:arch] = m[3].to_s - end - true - } - - # solaris family - plat.family('solaris').in_family('unix') - .detect { - if unix_uname_s =~ /sunos/i - unless (version = /^5\.(?\d+)$/.match(unix_uname_r)).nil? - @platform[:release] = version['release'] - end - - arch = @backend.run_command('uname -p') - @platform[:arch] = arch.stdout.chomp if arch.exit_status == 0 - true - end - } - plat.name('smartos').title('SmartOS').in_family('solaris') - .detect { - rel = unix_file_contents('/etc/release') - if /^.*(SmartOS).*$/ =~ rel - true - end - } - plat.name('omnios').title('Omnios').in_family('solaris') - .detect { - rel = unix_file_contents('/etc/release') - if !(m = /^\s*(OmniOS).*r(\d+).*$/.match(rel)).nil? - @platform[:release] = m[2] - true - end - } - plat.name('openindiana').title('Openindiana').in_family('solaris') - .detect { - rel = unix_file_contents('/etc/release') - if !(m = /^\s*(OpenIndiana).*oi_(\d+).*$/.match(rel)).nil? - @platform[:release] = m[2] - true - end - } - plat.name('opensolaris').title('Open Solaris').in_family('solaris') - .detect { - rel = unix_file_contents('/etc/release') - if !(m = /^\s*(OpenSolaris).*snv_(\d+).*$/.match(rel)).nil? - @platform[:release] = m[2] - true - end - } - plat.name('nexentacore').title('Nexentacore').in_family('solaris') - .detect { - rel = unix_file_contents('/etc/release') - if /^\s*(NexentaCore)\s.*$/ =~ rel - true - end - } - plat.name('solaris').title('Solaris').in_family('solaris') - .detect { - rel = unix_file_contents('/etc/release') - if !(m = /Oracle Solaris (\d+)/.match(rel)).nil? - # TODO: should be string! - @platform[:release] = m[1] - true - elsif /^\s*(Solaris)\s.*$/ =~ rel - true - end - - # must be some unknown solaris - true - } - - # hpux - plat.family('hpux').in_family('unix') - .detect { - true if unix_uname_s =~ /hp-ux/i - } - plat.name('hpux').title('Hpux').in_family('hpux') - .detect { - @platform[:release] = unix_uname_r.lines[0].chomp - true - } - - # bsd family - plat.family('bsd').in_family('unix') - .detect { - # we need a better way to determin this family - # for now we are going to just try each platform - true - } - plat.name('darwin').title('Darwin').in_family('bsd') - .detect { - cmd = unix_file_contents('/usr/bin/sw_vers') - if unix_uname_s =~ /darwin/i || !cmd.nil? - @platform[:name] = unix_uname_s.lines[0].chomp - @platform[:release] = cmd[/^ProductVersion:\s+(.+)$/, 1] || unix_uname_r.lines[0].chomp - @platform[:build] = cmd[/^BuildVersion:\s+(.+)$/, 1] - @platform[:arch] = unix_uname_m - true - end - } - plat.name('freebsd').title('Freebsd').in_family('bsd') - .detect { - if unix_uname_s =~ /freebsd/i - @platform[:name] = unix_uname_s.lines[0].chomp - @platform[:release] = unix_uname_r.lines[0].chomp - true - end - } - plat.name('openbsd').title('Openbsd').in_family('bsd') - .detect { - if unix_uname_s =~ /openbsd/i - @platform[:name] = unix_uname_s.lines[0].chomp - @platform[:release] = unix_uname_r.lines[0].chomp - true - end - } - plat.name('netbsd').title('Netbsd').in_family('bsd') - .detect { - if unix_uname_s =~ /netbsd/i - @platform[:name] = unix_uname_s.lines[0].chomp - @platform[:release] = unix_uname_r.lines[0].chomp - true - end - } - end - end -end diff --git a/lib/train/platforms/family.rb b/lib/train/platforms/family.rb deleted file mode 100644 index 61eb0d04..00000000 --- a/lib/train/platforms/family.rb +++ /dev/null @@ -1,26 +0,0 @@ -# encoding: utf-8 - -module Train::Platforms - class Family - include Train::Platforms::Common - attr_accessor :children, :condition, :families, :name - - def initialize(name, condition) - @name = name - @condition = condition - @families = {} - @children = {} - @detect = nil - @title = "#{name.to_s.capitalize} Family" - - # add itself to the families list - Train::Platforms.families[@name.to_s] = self - end - - def title(title = nil) - return @title if title.nil? - @title = title - self - end - end -end diff --git a/lib/train/platforms/platform.rb b/lib/train/platforms/platform.rb deleted file mode 100644 index a86edf32..00000000 --- a/lib/train/platforms/platform.rb +++ /dev/null @@ -1,80 +0,0 @@ -# encoding: utf-8 - -module Train::Platforms - class Platform - include Train::Platforms::Common - attr_accessor :backend, :condition, :families, :family_hierarchy, :platform - - def initialize(name, condition = {}) - @name = name - @condition = condition - @families = {} - @family_hierarchy = [] - @platform = {} - @detect = nil - @title = name.to_s.capitalize - - # add itself to the platform list - Train::Platforms.list[name] = self - end - - def direct_families - @families.collect { |k, _v| k.name } - end - - def name - # Override here incase a updated name was set - # during the detect logic - @platform[:name] || @name - end - - # This is for backwords compatability with - # the current inspec os resource. - def[](name) - if respond_to?(name) - send(name) - else - 'unknown' - end - end - - def title(title = nil) - return @title if title.nil? - @title = title - self - end - - def to_hash - @platform - end - - # Add generic family? and platform methods to an existing platform - # - # This is done later to add any custom - # families/properties that were created - def add_platform_methods - family_list = Train::Platforms.families - family_list.each_value do |k| - next if respond_to?(k.name + '?') - define_singleton_method(k.name + '?') do - family_hierarchy.include?(k.name) - end - end - - # Helper methods for direct platform info - @platform.each_key do |m| - next if respond_to?(m) - define_singleton_method(m) do - @platform[m] - end - end - - # Create method for name if its not already true - plat_name = name.downcase.tr(' ', '_') + '?' - return if respond_to?(plat_name) - define_singleton_method(plat_name) do - true - end - end - end -end diff --git a/lib/train/plugins/base_connection.rb b/lib/train/plugins/base_connection.rb index c2620071..ecf87ac4 100644 --- a/lib/train/plugins/base_connection.rb +++ b/lib/train/plugins/base_connection.rb @@ -1,4 +1,8 @@ # encoding: utf-8 +# +# Author:: Salim Afiune () +# Author:: Fletcher Nichol () +# Author:: Dominik Richter () require 'train/errors' require 'train/extras' @@ -26,7 +30,6 @@ def initialize(options = nil) @options = options || {} @logger = @options.delete(:logger) || Logger.new(STDOUT) @files = {} - Train::Platforms::Detect::Specifications::OS.load end # Closes the session connection, if it is still active. @@ -47,11 +50,6 @@ def load_json(j) end end - # Is this a local transport? - def local? - false - end - # Execute a command using this connection. # # @param command [String] command string to execute @@ -62,14 +60,11 @@ def run_command(_command) # Get information on the operating system which this transport connects to. # - # @return [Platform] system information - def platform - @platform ||= Train::Platforms::Detect.scan(self) + # @return [OSCommon] operating system information + def os + fail Train::ClientError, "#{self.class} does not implement #os()" end - # we need to keep os as a method for backwards compatibility with inspec - alias os platform - # Interact with files on the target. Read, write, and get metadata # from files via the transport. # diff --git a/lib/train/transports/docker.rb b/lib/train/transports/docker.rb index 75ce59b8..6af3204b 100644 --- a/lib/train/transports/docker.rb +++ b/lib/train/transports/docker.rb @@ -69,6 +69,10 @@ def close # nothing to do at the moment end + def os + @os ||= OS.new(self) + end + def file(path) @files[path] ||=\ if os.aix? @@ -101,5 +105,13 @@ def uri "docker://#{@container.id}" end end + + class OS < OSCommon + def initialize(backend) + # hardcoded to unix/linux for now, until other operating systems + # are supported + super(backend, { family: 'unix' }) + end + end end end diff --git a/lib/train/transports/local.rb b/lib/train/transports/local.rb index 1eeac343..60aef601 100644 --- a/lib/train/transports/local.rb +++ b/lib/train/transports/local.rb @@ -17,6 +17,8 @@ def connection(_ = nil) end class Connection < BaseConnection + require 'train/transports/local_os' + def initialize(options) super(options) @cmd_wrapper = nil @@ -32,8 +34,8 @@ def run_command(cmd) CommandResult.new('', '', 1) end - def local? - true + def os + @os ||= OS.new(self) end def file(path) diff --git a/lib/train/transports/local_os.rb b/lib/train/transports/local_os.rb new file mode 100644 index 00000000..72bc5088 --- /dev/null +++ b/lib/train/transports/local_os.rb @@ -0,0 +1,51 @@ +# encoding: utf-8 +# author: Dominik Richter +# author: Christoph Hartmann +# +# This is heavily based on: +# +# OHAI https://github.com/chef/ohai +# by Adam Jacob, Chef Software Inc +# + +require 'rbconfig' + +class Train::Transports::Local + class OS < OSCommon + def initialize(backend) + super(backend, { family: detect_local_os }) + end + + private + + def detect_local_os + case ::RbConfig::CONFIG['host_os'] + when /aix(.+)$/ + return 'aix' + when /darwin(.+)$/ + return 'darwin' + when /hpux(.+)$/ + return 'hpux' + when /linux/ + return 'linux' + when /freebsd(.+)$/ + return 'freebsd' + when /openbsd(.+)$/ + return 'openbsd' + when /netbsd(.*)$/ + return 'netbsd' + when /solaris2/ + return 'solaris2' + when /mswin|mingw32|windows/ + # After long discussion in IRC the "powers that be" have come to a consensus + # that no Windows platform exists that was not based on the + # Windows_NT kernel, so we herby decree that "windows" will refer to all + # platforms built upon the Windows_NT kernel and have access to win32 or win64 + # subsystems. + return 'windows' + else + return ::RbConfig::CONFIG['host_os'] + end + end + end +end diff --git a/lib/train/transports/mock.rb b/lib/train/transports/mock.rb index ffc56529..46aa0776 100644 --- a/lib/train/transports/mock.rb +++ b/lib/train/transports/mock.rb @@ -1,4 +1,7 @@ # encoding: utf-8 +# +# author: Dominik Richter +# author: Christoph Hartmann require 'train/plugins' require 'digest' @@ -31,7 +34,7 @@ def trace_calls 'Train::Transports::Mock::Connection::File' => Connection::FileCommon.instance_methods(false), 'Train::Transports::Mock::Connection::OS' => - Train::Platform.instance_methods(false), + Connection::OSCommon.instance_methods(false), } # rubocop:disable Metrics/ParameterLists @@ -71,19 +74,8 @@ def uri end def mock_os(value = {}) - value[:name] = 'unknown' unless value[:name] - platform = Train::Platforms.name(value[:name]) - platform.family_hierarchy = mock_os_hierarchy(platform).flatten - platform.platform[:family] = platform.family_hierarchy[0] - platform.add_platform_methods - @os = platform - end - - def mock_os_hierarchy(plat) - plat.families.each_with_object([]) do |(k, _v), memo| - memo << k.name - memo << mock_os_hierarchy(k) unless k.families.empty? - end + os_params = { name: 'unknown', family: 'unknown', release: 'unknown', arch: 'unknown' }.merge(value) + @os = OS.new(self, os_params) end def mock_command(cmd, stdout = nil, stderr = nil, exit_status = 0) @@ -125,6 +117,18 @@ class Train::Transports::Mock::Connection Command = Struct.new(:stdout, :stderr, :exit_status) end +class Train::Transports::Mock::Connection + class OS < OSCommon + def initialize(backend, desc) + super(backend, desc) + end + + def detect_family + # no op, we do not need to detect the os + end + end +end + class Train::Transports::Mock::Connection class File < Train::File def self.from_json(json) diff --git a/lib/train/transports/ssh_connection.rb b/lib/train/transports/ssh_connection.rb index 5e996796..7ea8f8cc 100644 --- a/lib/train/transports/ssh_connection.rb +++ b/lib/train/transports/ssh_connection.rb @@ -55,6 +55,10 @@ def close @session = nil end + def os + @os ||= OS.new(self) + end + def file(path) @files[path] ||= \ if os.aix? @@ -243,5 +247,11 @@ def to_s options_to_print[:password] = '' if options_to_print.key?(:password) "#{@username}@#{@hostname}<#{options_to_print.inspect}>" end + + class OS < OSCommon + def initialize(backend) + super(backend) + end + end end end diff --git a/lib/train/transports/winrm_connection.rb b/lib/train/transports/winrm_connection.rb index b856d743..a9eebd57 100644 --- a/lib/train/transports/winrm_connection.rb +++ b/lib/train/transports/winrm_connection.rb @@ -46,6 +46,10 @@ def close @session = nil end + def os + @os ||= OS.new(self) + end + def file(path) @files[path] ||= Train::File::Remote::Windows.new(self, path) end @@ -189,5 +193,11 @@ def to_s options_to_print[:password] = '' if options_to_print.key?(:password) "#{@username}@#{@hostname}<#{options_to_print.inspect}>" end + + class OS < OSCommon + def initialize(backend) + super(backend, { family: 'windows' }) + end + end end end diff --git a/test/unit/platforms/platform_test.rb b/test/unit/extras/os_common_test.rb similarity index 75% rename from test/unit/platforms/platform_test.rb rename to test/unit/extras/os_common_test.rb index 8025b1df..a0e8ac7a 100644 --- a/test/unit/platforms/platform_test.rb +++ b/test/unit/extras/os_common_test.rb @@ -1,102 +1,30 @@ # encoding: utf-8 +# author: Dominik Richter +# author: Christoph Hartmann require 'helper' +require 'train/extras' -describe 'platform' do - def mock_platform(x) - plat = Train::Platforms.name(x) - plat.family_hierarchy = mock_os_hierarchy(plat).flatten - plat.platform[:family] = plat.family_hierarchy[0] - plat.add_platform_methods - plat - end - - def mock_platform_family(x) - Train::Platforms.list[x] = nil if x == 'mock' - plat = Train::Platforms.name(x).in_family(x) - plat.family_hierarchy = mock_os_hierarchy(plat).flatten - plat.platform[:family] = plat.family_hierarchy[0] - plat.add_platform_methods - plat - end - - def mock_os_hierarchy(plat) - plat.families.each_with_object([]) do |(k, _v), memo| - memo << k.name - memo << mock_os_hierarchy(k) unless k.families.empty? +describe 'os common plugin' do + let(:cls) { + Class.new(Train::Extras::OSCommon) do + def detect_family; end end - end - - it 'set platform title' do - plat = mock_platform_family('mock') - plat.title.must_equal('Mock') - plat.title('The Best Mock') - plat.title.must_equal('The Best Mock') - end - - it 'set name and name override' do - plat = mock_platform_family('mock') - plat.name.must_equal('mock') - plat[:name].must_equal('mock') - plat.platform[:name] = 'mock2020' - plat.name.must_equal('mock2020') - plat[:name].must_equal('mock2020') - end - - it 'check families' do - plat = mock_platform_family('mock') - plat.families.keys[0].name.must_equal('mock') - end - - it 'check families with condition' do - Train::Platforms.list['mock'] = nil - plat = Train::Platforms.name('mock', arch: '= x86_64').in_family('linux') - plat.families.keys[0].name.must_equal('linux') - plat.families.values[0].must_equal({ arch: '= x86_64' }) - end - - it 'return direct families' do - plat = mock_platform_family('mock') - plat.in_family('mock2') - plat.in_family('mock3') - plat.direct_families.must_equal(["mock", "mock2", "mock3"]) - end - - it 'return to_hash' do - plat = mock_platform_family('mock') - plat.to_hash.must_equal({ family: "mock" }) - end + } - it 'return unknown release' do - plat = mock_platform_family('mock') - plat[:release].must_equal('unknown') - end - - it 'return name?' do - plat = Train::Platforms.name('windows_rc1') - defined?(plat.windows_rc1?).must_be_nil - plat.add_platform_methods - plat.windows_rc1?.must_equal(true) - end - - it 'add platform methods' do - Train::Platforms.list['mock'] = nil - plat = Train::Platforms.name('mock').in_family('linux') - defined?(plat.linux?).must_be_nil - plat.family_hierarchy = mock_os_hierarchy(plat).flatten - plat.add_platform_methods - plat.linux?.must_equal(true) + def mock_platform(x) + cls.new(nil, { family: x }) end it 'provides a method to access platform data' do - family = 'test-os' - os = mock_platform_family(family) + family = rand + os = mock_platform(family) os[:family].must_equal family end it 'provides an accessor for the full hash' do - x = 'test-os' - os = mock_platform_family(x) + x = rand.to_s + os = mock_platform(x) os.to_hash.must_equal({ family: x }) end @@ -140,7 +68,7 @@ def mock_os_hierarchy(plat) describe 'with platform set to amazon' do let(:os) { mock_platform('amazon') } it { os.fedora?.must_equal(false) } - it { os.redhat?.must_equal(true) } + it { os.redhat?.must_equal(false) } it { os.debian?.must_equal(false) } it { os.suse?.must_equal(false) } it { os.linux?.must_equal(true) } @@ -350,10 +278,10 @@ def mock_os_hierarchy(plat) end describe 'with platform set to esx' do - let(:os) { mock_platform('vmkernel') } + let(:os) { mock_platform('esx') } it { os.solaris?.must_equal(false) } it { os.linux?.must_equal(false) } - it { os[:family].must_equal('esx') } + it {os[:family].must_equal('esx')} it { os.unix?.must_equal(false) } it { os.esx?.must_equal(true) } end @@ -362,9 +290,10 @@ def mock_os_hierarchy(plat) let(:os) { mock_platform('darwin') } it { os.solaris?.must_equal(false) } it { os.linux?.must_equal(false) } - it { os[:family].must_equal('bsd') } + it {os[:family].must_equal('darwin')} it { os.unix?.must_equal(true) } it { os.bsd?.must_equal(true) } it { os.esx?.must_equal(false) } end + end diff --git a/test/unit/extras/os_detect_linux_test.rb b/test/unit/extras/os_detect_linux_test.rb new file mode 100644 index 00000000..bd5a7447 --- /dev/null +++ b/test/unit/extras/os_detect_linux_test.rb @@ -0,0 +1,230 @@ +# encoding: utf-8 +require 'helper' +require 'train/extras' + +class OsDetectLinuxTester + attr_reader :platform + include Train::Extras::DetectLinux + + def initialize + @platform = {} + end +end + +describe 'os_detect_linux' do + let(:detector) { OsDetectLinuxTester.new } + + describe '#detect_linux_arch' do + it "sets the arch using uname" do + be = mock("Backend") + detector.stubs(:backend).returns(be) + be.stubs(:run_command).with("uname -m").returns(mock("Output", stdout: "x86_64\n")) + detector.detect_linux_arch + detector.platform[:arch].must_equal("x86_64") + end + end + + describe '#detect_linux_via_config' do + + before do + detector.stubs(:get_config) + detector.stubs(:fetch_os_release) + detector.stubs(:redhatish_version).returns('redhat-version') + end + + describe '/etc/enterprise-release' do + it 'sets the correct family/release for oracle' do + detector.stubs(:get_config).with('/etc/enterprise-release').returns('data') + + detector.detect_linux_via_config.must_equal(true) + detector.platform[:name].must_equal('oracle') + detector.platform[:family].must_equal('redhat') + detector.platform[:release].must_equal('redhat-version') + end + end + + describe "/etc/redhat-release" do + describe "and /etc/os-release" do + it "sets the correct family, name, and release on centos" do + detector.stubs(:get_config).with("/etc/redhat-release").returns("CentOS Linux release 7.2.1511 (Core) \n") + detector.stubs(:get_config).with("/etc/os-release").returns("NAME=\"CentOS Linux\"\nVERSION=\"7 (Core)\"\nID=\"centos\"\nID_LIKE=\"rhel fedora\"\n") + detector.detect_linux_via_config.must_equal(true) + detector.platform[:name].must_equal('centos') + detector.platform[:family].must_equal('redhat') + detector.platform[:release].must_equal('redhat-version') + end + end + end + + describe '/etc/debian_version' do + + before { detector.stubs(:get_config).with('/etc/debian_version').returns('deb-version') } + + describe 'ubuntu' do + it 'sets the correct family/release for ubuntu' do + detector.stubs(:lsb).returns({ id: 'ubuntu', release: 'ubuntu-release' }) + + detector.detect_linux_via_config.must_equal(true) + detector.platform[:name].must_equal('ubuntu') + detector.platform[:family].must_equal('debian') + detector.platform[:release].must_equal('ubuntu-release') + end + end + + describe 'linuxmint' do + it 'sets the correct family/release for ubuntu' do + detector.stubs(:lsb).returns({ id: 'linuxmint', release: 'mint-release' }) + + detector.detect_linux_via_config.must_equal(true) + detector.platform[:name].must_equal('linuxmint') + detector.platform[:family].must_equal('debian') + detector.platform[:release].must_equal('mint-release') + end + end + + describe 'raspbian' do + it 'sets the correct family/release for raspbian ' do + detector.stubs(:lsb).returns({ id: 'something_else', release: 'some_release' }) + detector.expects(:unix_file?).with('/usr/bin/raspi-config').returns(true) + + detector.detect_linux_via_config.must_equal(true) + detector.platform[:name].must_equal('raspbian') + detector.platform[:family].must_equal('debian') + detector.platform[:release].must_equal('deb-version') + end + end + + describe 'everything else' do + it 'sets the correct family/release for debian ' do + detector.stubs(:lsb).returns({ id: 'something_else', release: 'some_release' }) + detector.expects(:unix_file?).with('/usr/bin/raspi-config').returns(false) + + detector.detect_linux_via_config.must_equal(true) + detector.platform[:name].must_equal('debian') + detector.platform[:family].must_equal('debian') + detector.platform[:release].must_equal('deb-version') + end + end + end + + describe '/etc/coreos/update.conf' do + it 'sets the correct family/release for coreos' do + detector.stubs(:get_config).with('/etc/coreos/update.conf').returns('data') + detector.stubs(:lsb).returns({ id: 'Container Linux by CoreOS', release: 'coreos-version' }) + + detector.detect_linux_via_config.must_equal(true) + detector.platform[:name].must_equal('coreos') + detector.platform[:family].must_equal('coreos') + detector.platform[:release].must_equal('coreos-version') + end + end + + describe '/etc/os-release' do + describe 'when not on a wrlinux build' do + it 'does not set a platform family/release' do + detector.stubs(:fetch_os_release).returns({ 'ID_LIKE' => 'something_else' }) + + detector.detect_linux_via_config.must_equal(false) + detector.platform[:family].must_be_nil + detector.platform[:release].must_be_nil + end + end + + describe 'when on a wrlinux build' do + let(:data) do + { + 'ID_LIKE' => 'cisco-wrlinux', + 'VERSION' => 'cisco123' + } + end + + it 'sets the correct family/release for wrlinux' do + detector.stubs(:fetch_os_release).returns(data) + + detector.detect_linux_via_config.must_equal(true) + detector.platform[:name].must_equal('wrlinux') + detector.platform[:family].must_equal('redhat') + detector.platform[:release].must_equal('cisco123') + end + end + end + end + + describe '#fetch_os_release' do + describe 'when no os-release data is available' do + it 'returns nil' do + detector.expects(:get_config).with('/etc/os-release').returns(nil) + detector.fetch_os_release.must_be_nil + end + end + + describe 'when os-release data exists with no CISCO_RELEASE_INFO' do + let(:os_release) { { 'KEY1' => 'VALUE1' } } + + it 'returns a correct hash' do + detector.expects(:get_config).with('/etc/os-release').returns('os-release data') + detector.expects(:parse_os_release_info).with('os-release data').returns(os_release) + detector.fetch_os_release['KEY1'].must_equal('VALUE1') + end + end + + describe 'when os-release data exists with CISCO_RELEASE_INFO' do + let(:os_release) { { 'KEY1' => 'VALUE1', 'CISCO_RELEASE_INFO' => 'cisco_file' } } + let(:cisco_release) { { 'KEY1' => 'NEWVALUE1', 'KEY2' => 'VALUE2' } } + + it 'returns a correct hash' do + detector.expects(:get_config).with('/etc/os-release').returns('os-release data') + detector.expects(:get_config).with('cisco_file').returns('cisco data') + detector.expects(:parse_os_release_info).with('os-release data').returns(os_release) + detector.expects(:parse_os_release_info).with('cisco data').returns(cisco_release) + + os_info = detector.fetch_os_release + os_info['KEY1'].must_equal('NEWVALUE1') + os_info['KEY2'].must_equal('VALUE2') + end + end + end + + describe '#parse_os_release_info' do + describe 'when nil is supplied' do + it 'returns an empty hash' do + detector.parse_os_release_info(nil).must_equal({}) + end + end + + describe 'when unexpectedly-formatted data is supplied' do + let(:data) do + <<-EOL +blah blah +no good data here + EOL + end + + it 'returns an empty hash' do + detector.parse_os_release_info(nil).must_equal({}) + end + end + + describe 'when properly-formatted data is supplied' do + let(:data) do + <<-EOL +KEY1=value1 +KEY2= +KEY3=value3 +KEY4="value4 with spaces" +KEY5="value5 with a = sign" + EOL + end + + it 'parses the data correctly' do + parsed_data = detector.parse_os_release_info(data) + + parsed_data['KEY1'].must_equal('value1') + parsed_data.key?('KEY2').must_equal(false) + parsed_data['KEY3'].must_equal('value3') + parsed_data['KEY4'].must_equal('value4 with spaces') + parsed_data['KEY5'].must_equal('value5 with a = sign') + end + end + end +end diff --git a/test/unit/platforms/detect/os_windows_test.rb b/test/unit/extras/os_detect_windows_test.rb similarity index 97% rename from test/unit/platforms/detect/os_windows_test.rb rename to test/unit/extras/os_detect_windows_test.rb index 1cd1a3ab..d84d681e 100644 --- a/test/unit/platforms/detect/os_windows_test.rb +++ b/test/unit/extras/os_detect_windows_test.rb @@ -1,11 +1,8 @@ -# encoding: utf-8 - -require 'helper' -require 'train/transports/mock' +require 'train/extras' class OsDetectWindowsTester attr_reader :platform, :backend - include Train::Platforms::Detect::Helpers::Windows + include Train::Extras::DetectWindows def initialize @platform = {} diff --git a/test/unit/file/remote/linux_test.rb b/test/unit/file/remote/linux_test.rb index 1b566a51..28993ce4 100644 --- a/test/unit/file/remote/linux_test.rb +++ b/test/unit/file/remote/linux_test.rb @@ -7,14 +7,14 @@ let(:cls) { Train::File::Remote::Linux } let(:backend) { backend = Train::Transports::Mock.new.connection - backend.mock_os({ name: 'linux', family: 'unix' }) + backend.mock_os({ family: 'linux' }) backend } def mock_stat(args, out, err = '', code = 0) backend.mock_command( "stat #{args} 2>/dev/null --printf '%s\n%f\n%U\n%u\n%G\n%g\n%X\n%Y\n%C'", - out, err, code + out, err, code, ) end diff --git a/test/unit/platforms/detect/os_common_test.rb b/test/unit/platforms/detect/os_common_test.rb deleted file mode 100644 index b48f9861..00000000 --- a/test/unit/platforms/detect/os_common_test.rb +++ /dev/null @@ -1,85 +0,0 @@ -# encoding: utf-8 - -require 'helper' - -class OsDetectLinuxTester - attr_reader :platform - include Train::Platforms::Detect::Helpers::OSCommon - - def initialize - @platform = {} - end -end - -describe 'os_common' do - let(:detector) { OsDetectLinuxTester.new } - - describe 'winrm? check' do - it 'return winrm? true' do - require 'train/transports/winrm' - be = Train::Transports::WinRM::Connection.new(nil) - detector.instance_variable_set(:@backend, be) - detector.winrm?.must_equal(true) - end - - it 'return winrm? false' do - be = mock('Backend') - detector.instance_variable_set(:@backend, be) - detector.winrm?.must_equal(false) - end - end - - describe 'unix file contents' do - it 'return new file contents' do - be = mock('Backend') - output = mock('Output', exit_status: 0) - output.expects(:stdout).returns('test') - be.stubs(:run_command).with('test -f /etc/fstab && cat /etc/fstab').returns(output) - detector.instance_variable_set(:@backend, be) - detector.instance_variable_set(:@files, {}) - detector.unix_file_contents('/etc/fstab').must_equal('test') - end - - it 'return new file contents cached' do - be = mock('Backend') - detector.instance_variable_set(:@backend, be) - detector.instance_variable_set(:@files, { '/etc/profile' => 'test' }) - detector.unix_file_contents('/etc/profile').must_equal('test') - end - end - - describe 'unix file exist?' do - it 'file does exist' do - be = mock('Backend') - be.stubs(:run_command).with('test -f /etc/test').returns(mock('Output', exit_status: 0)) - detector.instance_variable_set(:@backend, be) - detector.unix_file_exist?('/etc/test').must_equal(true) - end - end - - describe '#detect_linux_arch' do - it 'uname m call' do - be = mock('Backend') - be.stubs(:run_command).with('uname -m').returns(mock('Output', stdout: "x86_64\n")) - detector.instance_variable_set(:@backend, be) - detector.instance_variable_set(:@uname, {}) - detector.unix_uname_m.must_equal('x86_64') - end - - it 'uname s call' do - be = mock('Backend') - be.stubs(:run_command).with('uname -s').returns(mock('Output', stdout: "linux")) - detector.instance_variable_set(:@backend, be) - detector.instance_variable_set(:@uname, {}) - detector.unix_uname_s.must_equal('linux') - end - - it 'uname r call' do - be = mock('Backend') - be.stubs(:run_command).with('uname -r').returns(mock('Output', stdout: "17.0.0\n")) - detector.instance_variable_set(:@backend, be) - detector.instance_variable_set(:@uname, {}) - detector.unix_uname_r.must_equal('17.0.0') - end - end -end diff --git a/test/unit/platforms/detect/os_linux_test.rb b/test/unit/platforms/detect/os_linux_test.rb deleted file mode 100644 index bbe42ba1..00000000 --- a/test/unit/platforms/detect/os_linux_test.rb +++ /dev/null @@ -1,124 +0,0 @@ -# encoding: utf-8 - -require 'helper' -require 'train/transports/mock' - -class OsDetectLinuxTester - include Train::Platforms::Detect::Helpers::OSCommon -end - -describe 'os_linux' do - let(:detector) { OsDetectLinuxTester.new } - - describe 'redhatish_platform cleaner' do - it 'normal redhat' do - detector.redhatish_platform('Red Hattter').must_equal('redhat') - end - - it 'custom redhat' do - detector.redhatish_platform('Centos Pro 11').must_equal('centos') - end - end - - describe 'redhatish_version cleaner' do - it 'normal rawhide' do - detector.redhatish_version('18 (Rawhide) Pro').must_equal('18 (rawhide)') - end - - it 'normal linux' do - detector.redhatish_version('derived from Ubuntu Linux 11').must_equal('11') - end - end - - describe 'lsb parse' do - it 'lsb config' do - lsb = "DISTRIB_ID=Ubuntu\nDISTRIB_RELEASE=14.06\nDISTRIB_CODENAME=xenial" - expect = { :id=>'Ubuntu', :release=>'14.06', :codename=>'xenial' } - detector.lsb_config(lsb).must_equal(expect) - end - - it 'lsb releasel' do - lsb = "Distributor ID: Ubuntu\nRelease: 14.06\nCodename: xenial" - expect = { :id=>'Ubuntu', :release=>'14.06', :codename=>'xenial' } - detector.lsb_release(lsb).must_equal(expect) - end - end - - describe '#linux_os_release' do - describe 'when no os-release data is available' do - it 'returns nil' do - detector.expects(:unix_file_contents).with('/etc/os-release').returns(nil) - detector.linux_os_release.must_be_nil - end - end - end - - describe 'when os-release data exists with no CISCO_RELEASE_INFO' do - let(:os_release) { { 'KEY1' => 'VALUE1' } } - - it 'returns a correct hash' do - detector.expects(:unix_file_contents).with('/etc/os-release').returns('os-release data') - detector.expects(:parse_os_release_info).with('os-release data').returns(os_release) - detector.linux_os_release['KEY1'].must_equal('VALUE1') - end - end - - describe 'when os-release data exists with CISCO_RELEASE_INFO' do - let(:os_release) { { 'KEY1' => 'VALUE1', 'CISCO_RELEASE_INFO' => 'cisco_file' } } - let(:cisco_release) { { 'KEY1' => 'NEWVALUE1', 'KEY2' => 'VALUE2' } } - - it 'returns a correct hash' do - detector.expects(:unix_file_contents).with('/etc/os-release').returns('os-release data') - detector.expects(:unix_file_contents).with('cisco_file').returns('cisco data') - detector.expects(:parse_os_release_info).with('os-release data').returns(os_release) - detector.expects(:parse_os_release_info).with('cisco data').returns(cisco_release) - - os_info = detector.linux_os_release - os_info['KEY1'].must_equal('NEWVALUE1') - os_info['KEY2'].must_equal('VALUE2') - end - end - - describe '#parse_os_release_info' do - describe 'when nil is supplied' do - it 'returns an empty hash' do - detector.parse_os_release_info(nil).must_equal({}) - end - end - - describe 'when unexpectedly-formatted data is supplied' do - let(:data) do - <<-EOL -blah blah -no good data here - EOL - end - - it 'returns an empty hash' do - detector.parse_os_release_info(nil).must_equal({}) - end - end - - describe 'when properly-formatted data is supplied' do - let(:data) do - <<-EOL -KEY1=value1 -KEY2= -KEY3=value3 -KEY4="value4 with spaces" -KEY5="value5 with a = sign" - EOL - end - - it 'parses the data correctly' do - parsed_data = detector.parse_os_release_info(data) - - parsed_data['KEY1'].must_equal('value1') - parsed_data.key?('KEY2').must_equal(false) - parsed_data['KEY3'].must_equal('value3') - parsed_data['KEY4'].must_equal('value4 with spaces') - parsed_data['KEY5'].must_equal('value5 with a = sign') - end - end - end -end diff --git a/test/unit/platforms/detect/scanner_test.rb b/test/unit/platforms/detect/scanner_test.rb deleted file mode 100644 index 6c6b29bc..00000000 --- a/test/unit/platforms/detect/scanner_test.rb +++ /dev/null @@ -1,61 +0,0 @@ -# encoding: utf-8 - -require 'helper' -require 'train/platforms/detect/scanner' -require 'train/transports/mock' - -describe 'scanner' do - let(:backend) { Train::Transports::Mock::Connection.new } - let(:scanner) { Train::Platforms::Detect::Scanner.new(backend) } - - describe 'scan family children' do - it 'return child' do - family = Train::Platforms.family('linux') - scanner.scan_family_children(family).name.must_equal('linux') - scanner.instance_variable_get(:@family_hierarchy).must_equal(['linux']) - end - - it 'return nil' do - family = Train::Platforms.family('fake-fam') - scanner.scan_family_children(family).must_be_nil - scanner.instance_variable_get(:@family_hierarchy).must_be_empty - end - end - - describe 'check condition' do - it 'return true equal' do - scanner.instance_variable_set(:@platform, { arch: 'x86_64' }) - scanner.check_condition({ arch: '= x86_64' }).must_equal(true) - end - - it 'return true greater then' do - scanner.instance_variable_set(:@platform, { release: '8.2' }) - scanner.check_condition({ release: '>= 7' }).must_equal(true) - end - - it 'return false greater then' do - scanner.instance_variable_set(:@platform, { release: '2.2' }) - scanner.check_condition({ release: '> 7' }).must_equal(false) - end - end - - describe 'get platform' do - it 'return empty platform' do - plat = Train::Platforms.name('linux') - plat = scanner.get_platform(plat) - plat.platform.must_equal({}) - plat.backend.must_equal(backend) - plat.family_hierarchy.must_equal([]) - end - - it 'return full platform' do - scanner.instance_variable_set(:@platform, { family: 'linux' }) - scanner.instance_variable_set(:@family_hierarchy, [ 'linux', 'unix' ]) - plat = Train::Platforms.name('linux') - plat = scanner.get_platform(plat) - plat.platform.must_equal({ family: 'linux' }) - plat.backend.must_equal(backend) - plat.family_hierarchy.must_equal([ 'linux', 'unix' ]) - end - end -end diff --git a/test/unit/platforms/family_test.rb b/test/unit/platforms/family_test.rb deleted file mode 100644 index 4b81a3bb..00000000 --- a/test/unit/platforms/family_test.rb +++ /dev/null @@ -1,32 +0,0 @@ -# encoding: utf-8 - -require 'helper' - -describe 'platform family' do - def mock_family(x) - Train::Platforms.families[x] = nil if x == 'mock' - Train::Platforms.family(x) - end - - it 'set family title' do - plat = mock_family('mock') - plat.title.must_equal('Mock Family') - plat.title('The Best Mock Family') - plat.title.must_equal('The Best Mock Family') - end - - it 'set family in a family' do - plat = mock_family('family1') - plat.in_family('family2') - plat.families.keys[0].name.must_equal('family2') - - plat = mock_family('family2') - plat.children.keys[0].name.must_equal('family1') - end - - it 'set family in a family with condition' do - plat = Train::Platforms.family('family4', arch: '= x68_64').in_family('family5') - plat.families.keys[0].name.must_equal('family5') - plat.families.values[0].must_equal({ arch: '= x68_64' }) - end -end diff --git a/test/unit/platforms/os_detect_test.rb b/test/unit/platforms/os_detect_test.rb deleted file mode 100644 index 5f043617..00000000 --- a/test/unit/platforms/os_detect_test.rb +++ /dev/null @@ -1,147 +0,0 @@ -# encoding: utf-8 -require 'helper' -require 'train/transports/mock' - -class OsDetectLinuxTester - include Train::Platforms::Detect::Helpers::OSCommon -end - -describe 'os_detect_linux' do - let(:detector) { OsDetectLinuxTester.new } - - def scan_with_files(uname, files) - mock = Train::Transports::Mock::Connection.new - mock.mock_command('uname -s', uname) - files.each do |path, data| - mock.mock_command("test -f #{path}") - mock.mock_command("test -f #{path} && cat #{path}", data) - end - Train::Platforms::Detect.scan(mock) - end - - ## Detect all linux distros - describe '/etc/enterprise-release' do - it 'sets the correct family/release for oracle' do - path = '/etc/enterprise-release' - platform = scan_with_files('linux', { path => 'release 7' }) - - platform[:name].must_equal('oracle') - platform[:family].must_equal('redhat') - platform[:release].must_equal('7') - end - end - - describe '/etc/redhat-release' do - describe 'and /etc/os-release' do - it 'sets the correct family, name, and release on centos' do - files = { - '/etc/redhat-release' => "CentOS Linux release 7.2.1511 (Core) \n", - '/etc/os-release' => "NAME=\"CentOS Linux\"\nVERSION=\"7 (Core)\"\nID=\"centos\"\nID_LIKE=\"rhel fedora\"\n", - } - platform = scan_with_files('linux', files) - platform[:name].must_equal('centos') - platform[:family].must_equal('redhat') - platform[:release].must_equal('7.2.1511') - end - end - end - - describe '/etc/debian_version' do - def debian_scan(id, version) - lsb_release = "DISTRIB_ID=#{id}\nDISTRIB_RELEASE=#{version}" - files = { - '/etc/lsb-release' => lsb_release, - '/etc/debian_version' => 'data', - } - scan_with_files('linux', files) - end - - describe 'ubuntu' do - it 'sets the correct family/release for ubuntu' do - platform = debian_scan('ubuntu', '16.04') - - platform[:name].must_equal('ubuntu') - platform[:family].must_equal('debian') - platform[:release].must_equal('16.04') - end - end - - describe 'linuxmint' do - it 'sets the correct family/release for linuxmint' do - platform = debian_scan('linuxmint', '12') - - platform[:name].must_equal('linuxmint') - platform[:family].must_equal('debian') - platform[:release].must_equal('12') - end - end - - describe 'raspbian' do - it 'sets the correct family/release for raspbian ' do - files = { - '/usr/bin/raspi-config' => 'data', - '/etc/debian_version' => '13.6', - } - platform = scan_with_files('linux', files) - - platform[:name].must_equal('raspbian') - platform[:family].must_equal('debian') - platform[:release].must_equal('13.6') - end - end - - describe 'everything else' do - it 'sets the correct family/release for debian ' do - platform = debian_scan('some_debian', '12.99') - - platform[:name].must_equal('debian') - platform[:family].must_equal('debian') - platform[:release].must_equal('12.99') - end - end - end - - describe '/etc/coreos/update.conf' do - it 'sets the correct family/release for coreos' do - lsb_release = "DISTRIB_ID=Container Linux by CoreOS\nDISTRIB_RELEASE=27.9" - files = { - '/etc/lsb-release' => lsb_release, - '/etc/coreos/update.conf' => 'data', - } - platform = scan_with_files('linux', files) - - platform[:name].must_equal('coreos') - platform[:family].must_equal('linux') - platform[:release].must_equal('27.9') - end - end - - describe '/etc/os-release' do - describe 'when not on a wrlinux build' do - it 'fail back to genaric linux' do - os_release = "ID_LIKE=cisco-unkwown\nVERSION=unknown" - files = { - '/etc/os-release' => os_release, - } - platform = scan_with_files('linux', files) - - platform[:name].must_equal('linux') - platform[:family].must_equal('linux') - end - end - - describe 'when on a wrlinux build' do - it 'sets the correct family/release for wrlinux' do - os_release = "ID_LIKE=cisco-wrlinux\nVERSION=cisco123" - files = { - '/etc/os-release' => os_release, - } - platform = scan_with_files('linux', files) - - platform[:name].must_equal('wrlinux') - platform[:family].must_equal('redhat') - platform[:release].must_equal('cisco123') - end - end - end -end diff --git a/test/unit/platforms/platforms_test.rb b/test/unit/platforms/platforms_test.rb deleted file mode 100644 index 2712101c..00000000 --- a/test/unit/platforms/platforms_test.rb +++ /dev/null @@ -1,42 +0,0 @@ -# encoding: utf-8 - -require 'helper' - -describe 'platforms' do - - it 'create platform' do - Train::Platforms.list['mock'] = nil - plat = Train::Platforms.name('mock') - Train::Platforms.name('mock').in_family('test') - Train::Platforms.name('mock').detect { true } - plat.title.must_equal('Mock') - plat.detect.call.must_equal(true) - plat.families.keys[0].name.must_equal('test') - end - - it 'create family' do - Train::Platforms.families['mock'] = nil - fam = Train::Platforms.family('mock') - Train::Platforms.family('mock').in_family('test') - Train::Platforms.family('mock').detect { true } - fam.title.must_equal('Mock Family') - fam.detect.call.must_equal(true) - fam.families.keys[0].name.must_equal('test') - end - - it 'return top platforms empty' do - Train::Platforms.stubs(:list).returns({}) - Train::Platforms.stubs(:families).returns({}) - top = Train::Platforms.top_platforms - top.count.must_equal(0) - end - - it 'return top platforms with data' do - plat = Train::Platforms.name('linux') - plat.stubs(:families).returns({}) - Train::Platforms.stubs(:list).returns({ 'linux' => plat }) - Train::Platforms.stubs(:families).returns({}) - top = Train::Platforms.top_platforms - top.count.must_equal(1) - end -end diff --git a/test/unit/transports/local_test.rb b/test/unit/transports/local_test.rb index 9669c82b..2e7f9c1d 100644 --- a/test/unit/transports/local_test.rb +++ b/test/unit/transports/local_test.rb @@ -1,22 +1,27 @@ # encoding: utf-8 +# +# author: Dominik Richter +# author: Christoph Hartmann require 'helper' require 'train/transports/local' -class TransportHelper - attr_accessor :transport +# overwrite os detection to simplify mock tests, otherwise run_command tries to +# determine the OS first and fails the tests +class Train::Transports::Local::Connection + class OS < OSCommon + def initialize(backend) + super(backend, { family: 'train_mock_os' }) + end - def initialize - Train::Platforms::Detect::Specifications::OS.load - plat = Train::Platforms.name('mock').in_family('linux') - plat.add_platform_methods - Train::Platforms::Detect.stubs(:scan).returns(plat) - @transport = Train::Transports::Local.new + def detect_family + # no op, we do not need to detect the os + end end end describe 'local transport' do - let(:transport) { TransportHelper.new.transport } + let(:transport) { Train::Transports::Local.new } let(:connection) { transport.connection } it 'can be instantiated' do @@ -28,7 +33,7 @@ def initialize end it 'provides a uri' do - connection.uri.must_equal 'local://' + connection.uri.must_equal "local://" end it 'doesnt wait to be read' do diff --git a/test/unit/transports/mock_test.rb b/test/unit/transports/mock_test.rb index 1b58a9fc..62893d8f 100644 --- a/test/unit/transports/mock_test.rb +++ b/test/unit/transports/mock_test.rb @@ -72,14 +72,14 @@ describe 'when accessing a mocked os' do it 'has the default mock os faily set to unknown' do - connection.os[:name].must_equal 'unknown' + connection.os[:family].must_equal 'unknown' end it 'sets the OS to the mocked value' do - connection.mock_os({ name: 'centos' }) + connection.mock_os({ family: 'centos' }) connection.os.linux?.must_equal true connection.os.redhat?.must_equal true - connection.os[:family].must_equal 'redhat' + connection.os[:family].must_equal 'centos' end end diff --git a/test/unit/transports/ssh_test.rb b/test/unit/transports/ssh_test.rb index ebb4637f..513b0751 100644 --- a/test/unit/transports/ssh_test.rb +++ b/test/unit/transports/ssh_test.rb @@ -1,15 +1,23 @@ # encoding: utf-8 - require 'helper' require 'train/transports/ssh' -describe 'ssh transport' do - let(:cls) do - plat = Train::Platforms.name('mock').in_family('linux') - plat.add_platform_methods - Train::Platforms::Detect.stubs(:scan).returns(plat) - Train::Transports::SSH +# overwrite os detection to simplify mock tests, otherwise run_command tries to +# determine the OS first and fails the tests +class Train::Transports::SSH::Connection + class OS < OSCommon + def initialize(backend) + super(backend, { family: 'train_mock_os' }) + end + + def detect_family + # no op, we do not need to detect the os + end end +end + +describe 'ssh transport' do + let(:cls) { Train::Transports::SSH } let(:conf) {{ host: rand.to_s, password: rand.to_s,