Merge pull request #55 from chef/unit+lint

Start Linting remaining resources
This commit is contained in:
Christoph Hartmann 2015-09-26 20:00:34 +02:00
commit 308dbef369
15 changed files with 647 additions and 488 deletions

View file

@ -1,5 +1,14 @@
#
# Cookbook Name:: build-cookbook
# Recipe:: default
# Recipe:: lint
#
# Copyright (c) 2015 The Authors, All Rights Reserved.
# Copyright (c) 2015 Chef Software Inc., All Rights Reserved.
# Author:: Dominik Richter
include_recipe 'build-cookbook::prepare'
execute 'rubocop' do
command 'bundle exec rubocop -D'
cwd node['delivery_builder']['repo']
user node['delivery_builder']['build_user']
end

View file

@ -0,0 +1,21 @@
#
# Cookbook Name:: build-cookbook
# Recipe:: prepare
#
# Copyright (c) 2015 Chef Software Inc., All Rights Reserved.
# Author:: Dominik Richter
repo_dir = node['delivery_builder']['repo']
cache_dir = File.join(repo_dir, '.cache')
directory cache_dir do
owner node['delivery_builder']['build_user']
mode '0755'
end
execute 'bundle install' do
command 'bundle install --without=integration --without=tools --path='+cache_dir
cwd repo_dir
user node['delivery_builder']['build_user']
end

View file

@ -1,5 +1,14 @@
#
# Cookbook Name:: build-cookbook
# Recipe:: default
# Recipe:: unit
#
# Copyright (c) 2015 The Authors, All Rights Reserved.
# Copyright (c) 2015 Chef Software Inc., All Rights Reserved.
# Author:: Dominik Richter
include_recipe 'build-cookbook::prepare'
execute 'rake test' do
command 'bundle exec rake'
cwd node['delivery_builder']['repo']
user node['delivery_builder']['build_user']
end

View file

@ -5,8 +5,11 @@
"path": ".delivery/build-cookbook"
},
"skip_phases": [
"syntax",
"security",
"quality",
"smoke",
"quality"
"deploy"
],
"build_nodes": {}
}

View file

@ -2,22 +2,21 @@
# copyright: 2015, Vulcano Security GmbH
# license: All rights reserved
$__SCOPE = self
class MysqlSession < Vulcano.resource(1)
name 'mysql_session'
def initialize(user, pass)
@user = user
@pass = pass
initialize_fallback if user.nil? or pass.nil?
init_fallback if user.nil? or pass.nil?
skip_resource("Can't run MySQL SQL checks without authentication") if @user.nil? or @pass.nil?
end
def describe(query, db = '', &block)
def query(q, db = '')
# TODO: simple escape, must be handled by a library
# that does this securely
escaped_query = query.gsub(/\\/, '\\\\').gsub(/"/, '\\"').gsub(/\$/, '\\$')
escaped_query = q.gsub(/\\/, '\\\\').gsub(/"/, '\\"').gsub(/\$/, '\\$')
# run the query
cmd = vulcano.run_command("mysql -u#{@user} -p#{@pass} #{db} -s -e \"#{escaped_query}\"")
out = cmd.stdout + "\n" + cmd.stderr
@ -25,14 +24,15 @@ class MysqlSession < Vulcano.resource(1)
out.downcase =~ /^error/
# skip this test if the server can't run the query
skip_resource("Can't connect to MySQL instance for SQL checks.")
else
$__SCOPE.describe(cmd, &block)
end
# return the raw command output
cmd
end
private
def initialize_fallback
def init_fallback
# support debian mysql administration login
debian = vulcano.run_command('test -f /etc/mysql/debian.cnf && cat /etc/mysql/debian.cnf').stdout
return if debian.empty?

View file

@ -158,54 +158,61 @@ class LinuxPorts < PortsInfo
return nil if cmd.exit_status.to_i != 0
ports = []
# split on each newline
# parse all lines
cmd.stdout.each_line do |line|
# parse each line
# 1 - Proto, 2 - Recv-Q, 3 - Send-Q, 4 - Local Address, 5 - Foreign Address, 6 - State, 7 - Inode, 8 - PID/Program name
parsed = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$/.match(line)
port_info = parse_netstat_line(line)
if !parsed.nil?
protocol = parsed[1].downcase
# parse ip4 and ip6 addresses
net_addr = parsed[4]
if protocol.eql?('tcp6') || protocol.eql?('udp6')
# prep for URI parsing, parse ip6 port
ip6 = /^(\S+:)(\d+)$/.match(net_addr)
ip6addr = ip6[1]
ip6addr = '::' if /^:::$/.match(ip6addr)
# build uri
ip_addr = URI('addr://[' + ip6addr +']:' + ip6[2])
# replace []
host = ip_addr.host[1..ip_addr.host.size-2]
port = ip_addr.port
else
ip_addr = URI('addr://'+net_addr)
host = ip_addr.host
port = ip_addr.port
end
# extract PID
process = parsed[9].split('/')
pid = process[0]
pid = pid.to_i if /^\d+$/.match(pid)
process = process[1]
# map data
port_info = {
port: port,
address: host,
protocol: protocol,
process: process,
pid: pid,
}
# push data, if its a known protocol tcp, tcp6, udp, udp6
ports.push(port_info) if %w{tcp tcp6 udp udp6}.include?(protocol)
end
# only push protocols we are interested in
next unless %w{tcp tcp6 udp udp6}.include?(port_info[:protocol])
ports.push(port_info)
end
ports
end
def parse_net_address(net_addr, protocol)
if protocol.eql?('tcp6') || protocol.eql?('udp6')
# prep for URI parsing, parse ip6 port
ip6 = /^(\S+:)(\d+)$/.match(net_addr)
ip6addr = ip6[1]
ip6addr = '::' if /^:::$/.match(ip6addr)
# build uri
ip_addr = URI('addr://[' + ip6addr +']:' + ip6[2])
# replace []
host = ip_addr.host[1..ip_addr.host.size-2]
port = ip_addr.port
else
ip_addr = URI('addr://'+net_addr)
host = ip_addr.host
port = ip_addr.port
end
[host, port]
end
def parse_netstat_line(line)
# parse each line
# 1 - Proto, 2 - Recv-Q, 3 - Send-Q, 4 - Local Address, 5 - Foreign Address, 6 - State, 7 - Inode, 8 - PID/Program name
parsed = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$/.match(line)
return {} if parsed.nil?
# parse ip4 and ip6 addresses
protocol = parsed[1].downcase
host, port = parse_net_address(parsed[4], protocol)
# extract PID
process = parsed[9].split('/')
pid = process[0]
pid = pid.to_i if /^\d+$/.match(pid)
process = process[1]
# map data
{
port: port,
address: host,
protocol: protocol,
process: process,
pid: pid,
}
end
end
# extracts information from sockstat
@ -217,58 +224,66 @@ class FreeBsdPorts < PortsInfo
ports = []
# split on each newline
cmd.stdout.each_line do |line|
# 1 - USER, 2 - COMMAND, 3 - PID, 4 - FD 5 - PROTO, 6 - LOCAL ADDRESS, 7 - FOREIGN ADDRESS
parsed = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$/.match(line)
port_info = parse_sockstat_line(line)
if !parsed.nil?
protocol = parsed[5].downcase
net_addr = parsed[6]
# extract ip information
case protocol
when 'tcp4', 'udp4'
# replace * with 0.0.0.0
net_addr = net_addr.gsub(/^\*:/, '0.0.0.0:') if /^*:(\d+)$/.match(net_addr)
ip_addr = URI('addr://'+net_addr)
host = ip_addr.host
port = ip_addr.port
when 'tcp6', 'udp6'
next if net_addr == '*:*' # abort for now
# replace * with 0:0:0:0:0:0:0:0
net_addr = net_addr.gsub(/^\*:/, '0:0:0:0:0:0:0:0:') if /^*:(\d+)$/.match(net_addr)
# extract port
ip6 = /^(\S+):(\d+)$/.match(net_addr)
ip6addr = ip6[1]
ip_addr = URI('addr://[' + ip6addr +']:' + ip6[2])
# replace []
host = ip_addr.host[1..ip_addr.host.size-2]
port = ip_addr.port
end
# extract process
process = parsed[2]
# extract PID
pid = parsed[3]
pid = pid.to_i if /^\d+$/.match(pid)
# map tcp4 and udp4
protocol = 'tcp' if protocol.eql?('tcp4')
protocol = 'udp' if protocol.eql?('udp4')
# map data
port_info = {
port: port,
address: host,
protocol: protocol,
process: process,
pid: pid,
}
# push data, if not headerfile
ports.push(port_info) if %w{tcp tcp6 udp udp6}.include?(protocol)
end
# push data, if not headerfile
next unless %w{tcp tcp6 udp udp6}.include?(port_info[:protocol])
ports.push(port_info)
end
ports
end
def parse_net_address(net_addr, protocol)
case protocol
when 'tcp4', 'udp4'
# replace * with 0.0.0.0
net_addr = net_addr.gsub(/^\*:/, '0.0.0.0:') if /^*:(\d+)$/.match(net_addr)
ip_addr = URI('addr://'+net_addr)
host = ip_addr.host
port = ip_addr.port
when 'tcp6', 'udp6'
return [] if net_addr == '*:*' # abort for now
# replace * with 0:0:0:0:0:0:0:0
net_addr = net_addr.gsub(/^\*:/, '0:0:0:0:0:0:0:0:') if /^*:(\d+)$/.match(net_addr)
# extract port
ip6 = /^(\S+):(\d+)$/.match(net_addr)
ip6addr = ip6[1]
ip_addr = URI('addr://[' + ip6addr +']:' + ip6[2])
# replace []
host = ip_addr.host[1..ip_addr.host.size-2]
port = ip_addr.port
end
[host, port]
end
def parse_sockstat_line(line)
# 1 - USER, 2 - COMMAND, 3 - PID, 4 - FD 5 - PROTO, 6 - LOCAL ADDRESS, 7 - FOREIGN ADDRESS
parsed = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$/.match(line)
return {} if parsed.nil?
# extract ip information
protocol = parsed[5].downcase
host, port = parse_net_address(parsed[6], protocol)
return {} if host.nil? or port.nil?
# extract process
process = parsed[2]
# extract PID
pid = parsed[3]
pid = pid.to_i if /^\d+$/.match(pid)
# map tcp4 and udp4
protocol = 'tcp' if protocol.eql?('tcp4')
protocol = 'udp' if protocol.eql?('udp4')
# map data
{
port: port,
address: host,
protocol: protocol,
process: process,
pid: pid,
}
end
end

View file

@ -22,12 +22,14 @@ class Service < Vulcano.resource(1)
def initialize(service_name)
@service_name = service_name
# select package manager
@service_mgmt = nil
@cache = nil
select_package_manager
end
def select_package_manager # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
family = vulcano.os[:family]
case family
# Ubuntu
# @see: https://wiki.ubuntu.com/SystemdForUpstartUsers
@ -309,8 +311,7 @@ class WindowsSrv < ServiceManager
# - 6: Pause Pending
# - 7: Paused
def info(service_name)
srv_cmd = "New-Object -Type PSObject | Add-Member -MemberType NoteProperty -Name Service -Value (Get-Service -Name #{service_name}| Select-Object -Property Name, DisplayName, Status) -PassThru | Add-Member -MemberType NoteProperty -Name WMI -Value (Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq '#{service_name}' -or $_.DisplayName -eq '#{service_name}'} | Select-Object -Property StartMode) -PassThru | ConvertTo-Json"
cmd = @vulcano.run_command(srv_cmd)
cmd = @vulcano.run_command("New-Object -Type PSObject | Add-Member -MemberType NoteProperty -Name Service -Value (Get-Service -Name #{service_name}| Select-Object -Property Name, DisplayName, Status) -PassThru | Add-Member -MemberType NoteProperty -Name WMI -Value (Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq '#{service_name}' -or $_.DisplayName -eq '#{service_name}'} | Select-Object -Property StartMode) -PassThru | ConvertTo-Json")
# cannot rely on exit code for now, successful command returns exit code 1
# return nil if cmd.exit_status != 0
@ -324,27 +325,36 @@ class WindowsSrv < ServiceManager
# check that we got a response
return nil if service.nil? || service['Service'].nil?
# detect if service is running
if !service['Service']['Status'].nil? && service['Service']['Status'] == 4
running = true
else
running = false
end
# detect if service is enabled
if !service['WMI'].nil? && !service['WMI']['StartMode'].nil? && service['WMI']['StartMode'] == 'Auto'
enabled = true
else
enabled = false
end
{
name: service['Service']['Name'],
description: service['Service']['DisplayName'],
installed: true,
running: running,
enabled: enabled,
running: service_running?(service),
enabled: service_enabled?(service),
type: 'windows',
}
end
private
# detect if service is enabled
def service_enabled?(service)
if !service['WMI'].nil? &&
!service['WMI']['StartMode'].nil? &&
service['WMI']['StartMode'] == 'Auto'
true
else
false
end
end
# detect if service is running
def service_running?(service)
if !service['Service']['Status'].nil? &&
service['Service']['Status'] == 4
true
else
false
end
end
end

View file

@ -14,12 +14,4 @@ require 'vulcano/resource'
require 'vulcano/rspec_json_formatter'
require 'vulcano/rule'
require 'vulcano/runner'
require 'matchers/matchers'
# Dummy module for handling additional attributes
# which may be injected by the user. This covers data
# like passwords, usernames, or configuration flags.
def attributes(what, required: false)
nil
end

View file

@ -0,0 +1,30 @@
# encoding: utf-8
#
# This is heavily based on:
#
# OHAI https://github.com/chef/ohai
# by Adam Jacob, Chef Software Inc
#
class Vulcano::Plugins::Backend
module DetectDarwin
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'
true
end
end
end

View file

@ -0,0 +1,116 @@
# encoding: utf-8
#
# This is heavily based on:
#
# OHAI https://github.com/chef/ohai
# by Adam Jacob, Chef Software Inc
#
require 'vulcano/plugins/backend_linux_lsb'
class Vulcano::Plugins::Backend
module DetectLinux
include LinuxLSB
def detect_linux_via_config # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
if !(raw = get_config('oracle-release')).nil?
@platform[:family] = 'oracle'
@platform[:release] = redhatish_version(raw)
elsif !(raw = get_config('/etc/enterprise-release')).nil?
@platform[:family] = 'oracle'
@platform[:release] = redhatish_version(raw)
elsif !(_raw = get_config('/etc/debian_version')).nil?
case lsb[:id]
when /ubuntu/i
@platform[:family] = 'ubuntu'
@platform[:release] = lsb[:release]
when /linuxmint/i
@platform[:family] = 'linuxmint'
@platform[:release] = lsb[:release]
else
@platform[:family] = 'debian'
@platform[:family] = 'raspbian' if unix_file?('/usr/bin/raspi-config')
if !(rel = get_config('/etc/debian_version')).nil?
@platform[:release] = rel.chomp
end
end
elsif !(raw = get_config('/etc/parallels-release')).nil?
@platform[:family] = redhatish_platform(raw)
@platform[:release] = raw[/(\d\.\d\.\d)/, 1]
elsif !(raw = get_config('/etc/redhat-release')).nil?
# TODO: Cisco
@platform[:family] = redhatish_platform(raw)
@platform[:release] = redhatish_version(raw)
elsif !(raw = get_config('/etc/system-release')).nil?
# Amazon Linux
@platform[:family] = 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[:family] = 'suse'
@platform[:family] = 'opensuse' if suse =~ /^openSUSE/
elsif !(raw = get_config('/etc/arch-release')).nil?
@platform[:family] = '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[:family] = 'slackware'
@platform[:release] = raw.scan(/(\d+|\.+)/).join
elsif !(raw = get_config('/etc/exherbo-release')).nil?
@platform[:family] = '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[:family] = 'gentoo'
@platform[:release] = raw.scan(/(\d+|\.+)/).join
elsif !(raw = get_config('/etc/alpine-release')).nil?
@platform[:family] = 'alpine'
@platform[:release] = raw.strip
elsif !(raw = get_config('/etc/coreos/update.conf')).nil?
@platform[:family] = 'coreos'
meta = lsb_config(raw)
@platform[:release] = meta[:release]
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 uname_s
@uname_s ||= @backend.run_command('uname -s').stdout
end
def uname_r
@uname_r ||= (
res = @backend.run_command('uname -r').stdout
res.strip! unless res.nil?
res
)
end
def redhatish_platform(conf)
conf[/^red hat/i] ? 'redhat' : conf[/(\w+)/i, 1].downcase
end
def redhatish_version(conf)
conf[/rawhide/i] ? conf[/((\d+) \(Rawhide\))/i, 1].downcase : conf[/release ([\d\.]+)/, 1]
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?
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
end
end

View file

@ -0,0 +1,75 @@
# encoding: utf-8
#
# This is heavily based on:
#
# OHAI https://github.com/chef/ohai
# by Adam Jacob, Chef Software Inc
#
class Vulcano::Plugins::Backend
module DetectUnix
def detect_via_uname # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
case uname_s.downcase
when /aix/
@platform[:family] = 'aix'
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 /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 /sunos/
@platform[:family] = 'solaris'
if uname_r =~ /5\.10/
# TODO: should be string!
@platform[:release] = 10
else
rel = get_config('/etc/release')
case rel
when /^.*(SmartOS).*$/
@platform[:family] = 'smartos'
when !(m = /^\s*(OmniOS).*r(\d+).*$/).nil?
@platform[:family] = 'omnios'
@platform[:release] = m[2]
when !(m = /^\s*(OpenIndiana).*oi_(\d+).*$/).nil?
@platform[:family] = 'openindiana'
@platform[:release] = m[2]
when /^\s*(OpenSolaris).*snv_(\d+).*$/
@platform[:family] = 'opensolaris'
@platform[:release] = m[2]
when !(m = /Oracle Solaris (\d+)/).nil?
# TODO: should be string!
@platform[:release] = m[1].to_i
@platform[:family] = 'solaris2'
when /^\s*(Solaris)\s.*$/
@platform[:family] = 'solaris2'
when /^\s*(NexentaCore)\s.*$/
@platform[:family] = 'nexentacore'
end
end
else
# in all other cases we didn't detect it
return false
end
# when we get here the detection returned a result
true
end
end
end

View file

@ -0,0 +1,70 @@
# encoding: utf-8
#
# This is heavily based on:
#
# OHAI https://github.com/chef/ohai
# by Adam Jacob, Chef Software Inc
#
class Vulcano::Plugins::Backend
module DetectWindows
# See: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724832%28v=vs.85%29.aspx
# Product Type:
# Work Station (1)
# Domain Controller (2)
# Server (3)
WINDOWS_VERSIONS = {
'0' => '3.1',
'140' => '95',
'1410' => '98',
'1490' => 'ME',
'1351' => 'NT 3.51',
'3351' => 'NT 3.51 Server',
'1240' => 'NT 4.0',
'3240' => 'NT 4.0 Server',
'1250' => '2000',
'1251' => 'XP',
'3252' => 'Server 2003',
'1252' => 'Vista',
'3260' => 'Server 2008',
'1261' => '7',
'3261' => 'Server 2008 R2',
'1262' => '8',
'3262' => 'Server 2012',
'1263' => '8.1',
'3263' => 'Server 2012 R2',
'12100' => '10',
'32100' => 'Server 2016',
}
def windows_version(json)
producttype = json['OS']['ProductType'].to_s
# do not distigush between domain controller and server
producttype = '3' if producttype == '2'
platform = json['OSVersion']['Platform'].to_s
major = json['OSVersion']['Version']['Major'].to_s
minor = json['OSVersion']['Version']['Minor'].to_s
# construct it
producttype + platform + major + minor
end
def detect_windows
cmd = 'New-Object -Type PSObject | Add-Member -MemberType NoteProperty '\
'-Name OS -Value (Get-WmiObject -Class Win32_OperatingSystem) '\
'-PassThru | Add-Member -MemberType NoteProperty -Name OSVersion '\
'-Value ([Environment]::OSVersion) -PassThru | ConvertTo-Json'
res = @backend.run_command(cmd)
# TODO: error as this shouldnt be happening at this point
return false if res.exit_status != 0 or res.stdout.empty?
json = JSON.parse(res.stdout)
return false if json.nil? or json.empty?
version = windows_version(json)
@platform[:family] = 'windows'
@platform[:name] = WINDOWS_VERSIONS[version]
@platform[:release] = version
true
end
end
end

View file

@ -0,0 +1,58 @@
# encoding: utf-8
#
# This is heavily based on:
#
# OHAI https://github.com/chef/ohai
# by Adam Jacob, Chef Software Inc
#
class Vulcano::Plugins::Backend
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 unless @lsb.nil?
@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

View file

@ -6,8 +6,18 @@
# by Adam Jacob, Chef Software Inc
#
require 'vulcano/plugins/backend_detect_darwin'
require 'vulcano/plugins/backend_detect_linux'
require 'vulcano/plugins/backend_detect_unix'
require 'vulcano/plugins/backend_detect_windows'
class Vulcano::Plugins::Backend
class OSCommon
include DetectDarwin
include DetectLinux
include DetectUnix
include DetectWindows
def initialize(backend, platform = nil)
@backend = backend
@platform = platform || {}
@ -53,7 +63,6 @@ class Vulcano::Plugins::Backend
pf = @platform[:family]
return detect_windos if pf == 'windows'
return detect_darwin if pf == 'darwin'
if %w{freebsd netbsd openbsd aix solaris2}.include?(pf)
@ -63,263 +72,10 @@ class Vulcano::Plugins::Backend
# unix based systems combine the above
return true if pf == 'unix' and (detect_darwin or detect_via_uname)
# if we arrive here, we most likey have a regular linux
detect_linux
end
# See: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724832%28v=vs.85%29.aspx
# Product Type:
# Work Station (1)
# Domain Controller (2)
# Server (3)
WindowsVersions = {
'0' => '3.1',
'140' => '95',
'1410' => '98',
'1490' => 'ME',
'1351' => 'NT 3.51',
'3351' => 'NT 3.51 Server',
'1240' => 'NT 4.0',
'3240' => 'NT 4.0 Server',
'1250' => '2000',
'1251' => 'XP',
'3252' => 'Server 2003',
'1252' => 'Vista',
'3260' => 'Server 2008',
'1261' => '7',
'3261' => 'Server 2008 R2',
'1262' => '8',
'3262' => 'Server 2012',
'1263' => '8.1',
'3263' => 'Server 2012 R2',
'12100' => '10',
'32100' => 'Server 2016',
}
def windows_version(json)
producttype = json['OS']['ProductType'].to_s
# do not distigush between domain controller and server
producttype = '3' if producttype == '2'
platform = json['OSVersion']['Platform'].to_s
major = json['OSVersion']['Version']['Major'].to_s
minor = json['OSVersion']['Version']['Minor'].to_s
# construct it
producttype + platform + major + minor
end
def detect_windows
cmd = 'New-Object -Type PSObject | Add-Member -MemberType NoteProperty '\
'-Name OS -Value (Get-WmiObject -Class Win32_OperatingSystem) '\
'-PassThru | Add-Member -MemberType NoteProperty -Name OSVersion '\
'-Value ([Environment]::OSVersion) -PassThru | ConvertTo-Json'
res = @backend.run_command(cmd)
# TODO: error as this shouldnt be happening at this point
return false if res.exit_status != 0 or res.stdout.empty?
json = JSON.parse(res.stdout)
return false if json.nil? or json.empty?
version = windows_version(json)
@platform[:family] = 'windows'
@platform[:name] = versions[version]
@platform[:release] = version
true
end
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'
true
end
def detect_via_uname
case uname_s.downcase
when /aix/
@platform[:family] = 'aix'
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 /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 /sunos/
@platform[:family] = 'solaris'
if uname_r =~ /5\.10/
# TODO: should be string!
@platform[:release] = 10
else
rel = get_config('/etc/release')
case rel
when /^.*(SmartOS).*$/
@platform[:family] = 'smartos'
when !(m = /^\s*(OmniOS).*r(\d+).*$/).nil?
@platform[:family] = 'omnios'
@platform[:release] = m[2]
when !(m = /^\s*(OpenIndiana).*oi_(\d+).*$/).nil?
@platform[:family] = 'openindiana'
@platform[:release] = m[2]
when /^\s*(OpenSolaris).*snv_(\d+).*$/
@platform[:family] = 'opensolaris'
@platform[:release] = m[2]
when !(m = /Oracle Solaris (\d+)/).nil?
# TODO: should be string!
@platform[:release] = m[1].to_i
@platform[:family] = 'solaris2'
when /^\s*(Solaris)\s.*$/
@platform[:family] = 'solaris2'
when /^\s*(NexentaCore)\s.*$/
@platform[:family] = 'nexentacore'
end
end
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_linux_via_config
if !(raw = get_config('oracle-release')).nil?
@platform[:family] = 'oracle'
@platform[:release] = redhatish_version(raw)
elsif !(raw = get_config('/etc/enterprise-release')).nil?
@platform[:family] = 'oracle'
@platform[:release] = redhatish_version(raw)
elsif !(_raw = get_config('/etc/debian_version')).nil?
case lsb[:id]
when /ubuntu/i
@platform[:family] = 'ubuntu'
@platform[:release] = lsb[:release]
when /linuxmint/i
@platform[:family] = 'linuxmint'
@platform[:release] = lsb[:release]
else
@platform[:family] = 'debian'
@platform[:family] = 'raspbian' if unix_file?('/usr/bin/raspi-config')
if !(rel = get_config('/etc/debian_version')).nil?
@platform[:release] = rel.chomp
end
end
elsif !(raw = get_config('/etc/parallels-release')).nil?
@platform[:family] = redhatish_platform(raw)
@platform[:release] = raw[/(\d\.\d\.\d)/, 1]
elsif !(raw = get_config('/etc/redhat-release')).nil?
# TODO: Cisco
@platform[:family] = redhatish_platform(raw)
@platform[:release] = redhatish_version(raw)
elsif !(raw = get_config('/etc/system-release')).nil?
# Amazon Linux
@platform[:family] = 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[:family] = 'suse'
@platform[:family] = 'opensuse' if suse =~ /^openSUSE/
elsif !(raw = get_config('/etc/arch-release')).nil?
@platform[:family] = '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[:family] = 'slackware'
@platform[:release] = raw.scan(/(\d+|\.+)/).join
elsif !(raw = get_config('/etc/exherbo-release')).nil?
@platform[:family] = '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[:family] = 'gentoo'
@platform[:release] = raw.scan(/(\d+|\.+)/).join
elsif !(raw = get_config('/etc/alpine-release')).nil?
@platform[:family] = 'alpine'
@platform[:release] = raw.strip
elsif !(raw = get_config('/etc/coreos/update.conf')).nil?
@platform[:family] = 'coreos'
meta = lsb_config(raw)
@platform[:release] = meta[:release]
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_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
def uname_s
@uname_s ||= @backend.run_command('uname -s').stdout
end
def uname_r
@uname_r ||= (
res = @backend.run_command('uname -r').stdout
res.strip! unless res.nil?
res
)
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?
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 get_config(path)
res = @backend.run_command("test -f #{path} && cat #{path}")
# ignore files that can't be read
@ -330,41 +86,5 @@ class Vulcano::Plugins::Backend
def unix_file?(path)
@backend.run_command("test -f #{path}").exit_status == 0
end
def redhatish_platform(conf)
conf[/^red hat/i] ? 'redhat' : conf[/(\w+)/i, 1].downcase
end
def redhatish_version(conf)
conf[/rawhide/i] ? conf[/((\d+) \(Rawhide\))/i, 1].downcase : conf[/release ([\d\.]+)/, 1]
end
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 unless @lsb.nil?
@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
end
end

View file

@ -15,70 +15,10 @@ module Vulcano
@profile_id = profile_id
@rules = profile_registry
@only_ifs = only_ifs
profile_context_owner = self
dsl = Module.new do
Vulcano::Resource.registry.each do |id, r|
define_method id.to_sym do |*args|
r.new(backend, *args)
end
end
end
rule_class = Class.new(Vulcano::Rule) do
include RSpec::Core::DSL
include dsl
end
outer_dsl = Class.new do
include dsl
define_method :rule do |*args, &block|
id = args[0]
opts = args[1] || {}
return if @skip_profile
__register_rule rule_class.new(id, opts, &block)
end
define_method :describe do |*args, &block|
path = block.source_location[0]
line = block.source_location[1]
id = "#{File.basename(path)}:#{line}"
rule = rule_class.new(id, {}) do
describe(*args, &block)
end
__register_rule rule, &block
end
def skip_rule(id)
__unregister_rule id
end
def only_if(&block)
return unless block_given?
@skip_profile = !block.call
end
end
# This is the heart of the profile context
# An instantiated object which has all resources registered to it
# and exposes them to the a test file.
# rubocop:disable Lint/NestedMethodDefinition
ctx = Class.new(outer_dsl) do
include Vulcano::DSL
define_method :__register_rule do |*args|
profile_context_owner.register_rule(*args)
end
define_method :__unregister_rule do |*args|
profile_context_owner.unregister_rule(*args)
end
def to_s
'Profile Context Run'
end
end
# rubocop:enable all
dsl = create_inner_dsl(backend)
outer_dsl = create_outer_dsl(dsl)
ctx = create_context(outer_dsl)
@profile_context = ctx.new
end
@ -107,5 +47,96 @@ module Vulcano
Vulcano::Rule.merge(existing, r)
end
end
private
# Creates the inner DSL which includes all resources for
# creating tests. It is always connected to one target,
# which is specified via the backend argument.
#
# @param backend [BackendRunner] exposing the target to resources
# @return [InnerDSLModule]
def create_inner_dsl(backend)
Module.new do
Vulcano::Resource.registry.each do |id, r|
define_method id.to_sym do |*args|
r.new(backend, *args)
end
end
end
end
# Creates the outer DSL which includes all methods for creating
# tests and control structures.
#
# @param dsl [InnerDSLModule] which contains all resources
# @return [OuterDSLClass]
def create_outer_dsl(dsl)
rule_class = Class.new(Vulcano::Rule) do
include RSpec::Core::DSL
include dsl
end
# rubocop:disable Lint/NestedMethodDefinition
Class.new do
include dsl
define_method :rule do |*args, &block|
id = args[0]
opts = args[1] || {}
return if @skip_profile
__register_rule rule_class.new(id, opts, &block)
end
define_method :describe do |*args, &block|
path = block.source_location[0]
line = block.source_location[1]
id = "#{File.basename(path)}:#{line}"
rule = rule_class.new(id, {}) do
describe(*args, &block)
end
__register_rule rule, &block
end
def skip_rule(id)
__unregister_rule id
end
def only_if(&block)
return unless block_given?
@skip_profile = !block.call
end
end
# rubocop:enable all
end
# Creates the heart of the profile context:
# An instantiated object which has all resources registered to it
# and exposes them to the a test file. The profile context serves as a
# container for all profiles which are registered. Within the context
# profiles get access to all DSL calls for creating tests and controls.
#
# @param outer_dsl [OuterDSLClass]
# @return [ProfileContextClass]
def create_context(outer_dsl)
profile_context_owner = self
# rubocop:disable Lint/NestedMethodDefinition
Class.new(outer_dsl) do
include Vulcano::DSL
define_method :__register_rule do |*args|
profile_context_owner.register_rule(*args)
end
define_method :__unregister_rule do |*args|
profile_context_owner.unregister_rule(*args)
end
def to_s
'Profile Context Run'
end
end
# rubocop:enable all
end
end
end