Add platform resource and platform supports (#2393)

* Add platform resource and platform supports.

Signed-off-by: Jared Quick <jquick@chef.io>

* Cache platform and inspec checks and implement inspec_version.

Signed-off-by: Jared Quick <jquick@chef.io>

* Deprecate current inspec support in favor of inspec_version.

Signed-off-by: Jared Quick <jquick@chef.io>

* Update resource/profile skip messages.

Signed-off-by: Jared Quick <jquick@chef.io>

* Update load_resource to use platform instead of os.

Signed-off-by: Jared Quick <jquick@chef.io>

* Update platform example.

Signed-off-by: Jared Quick <jquick@chef.io>
This commit is contained in:
Jared Quick 2018-01-02 14:04:13 -05:00 committed by Dominik Richter
parent 2a187530e5
commit 10dc5621fb
14 changed files with 355 additions and 95 deletions

View file

@ -117,10 +117,21 @@ module Inspec
end
define_method :register_control do |control, &block|
if @skip_file || !profile_context_owner.profile_supports_os?
if @skip_file
::Inspec::Rule.set_skip_rule(control, true)
end
unless profile_context_owner.profile_supports_platform?
platform = inspec.platform
msg = "Profile #{profile_context_owner.profile_id} is not supported on platform #{platform.name}/#{platform.release}."
::Inspec::Rule.set_skip_rule(control, msg)
end
unless profile_context_owner.profile_supports_inspec_version?
msg = "Profile #{profile_context_owner.profile_id} is not supported on InSpec version (#{Inspec::VERSION})."
::Inspec::Rule.set_skip_rule(control, msg)
end
profile_context_owner.register_rule(control, &block) unless control.nil?
end

View file

@ -36,6 +36,7 @@ module Inspec
summary
description
version
inspec_version
}.each do |name|
define_method name.to_sym do |arg|
params[name.to_sym] = arg
@ -52,38 +53,16 @@ module Inspec
# already.
end
def is_supported?(os, entry)
name = entry[:'os-name'] || entry[:os]
family = entry[:'os-family']
release = entry[:release]
# return true if the backend matches the supported OS's
# fields act as masks, i.e. any value configured for os-name, os-family,
# or release must be met by the backend; any field that is nil acts as
# a glob expression i.e. is true
# os name is both saved in :family and :name, so check both
name_ok = name.nil? ||
os[:name] == name || os[:family] == name
family_check = family.to_s + '?'
family_ok = family.nil? || os[:family] == family ||
(
os.respond_to?(family_check) &&
# this call will return true if the family matches
os.method(family_check).call
)
# ensure we do have a string if we have a non-nil value eg. 16.06
release_ok = release.nil? || os[:release] == release
# we want to make sure that all matchers are true
name_ok && family_ok && release_ok
end
def inspec_requirement
inspec = params[:supports].find { |x| !x[:inspec].nil? } || {}
Gem::Requirement.create(inspec[:inspec])
inspec_in_supports = params[:supports].find { |x| !x[:inspec].nil? }
if inspec_in_supports
Inspec::Log.warn '[DEPRECATED] The use of inspec.yml `supports:inspec` is deprecated and will be removed in InSpec 2.0. Please use `inspec_version` instead.'
Gem::Requirement.create(inspec_in_supports[:inspec])
else
# using Gem::Requirement here to allow nil values which
# translate to [">= 0"]
Gem::Requirement.create(params[:inspec_version])
end
end
def supports_runtime?
@ -91,18 +70,8 @@ module Inspec
inspec_requirement.satisfied_by?(running)
end
def supports_transport?(backend)
# with no supports specified, always return true, as there are no
# constraints on the supported backend; it is equivalent to putting
# all fields into accept-all mode
return true if params[:supports].empty?
found = params[:supports].find do |entry|
is_supported?(backend.os, entry)
end
# finally, if we found a supported entry, we are good to go
!found.nil?
def supports_platform?(backend)
backend.platform.supported?(params[:supports])
end
# return all warn and errors

View file

@ -28,6 +28,12 @@ module Inspec
__resource_registry[@name].desc(description)
end
def supports(criteria = nil)
return if criteria.nil?
Inspec::Resource.supports[@name] ||= []
Inspec::Resource.supports[@name].push(criteria)
end
def example(example = nil)
return if example.nil?
__resource_registry[@name].example(example)
@ -37,18 +43,22 @@ module Inspec
Inspec::Resource.registry
end
def __register(name, obj) # rubocop:disable Metrics/MethodLength
cl = Class.new(obj) do
def __register(name, obj) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
cl = Class.new(obj) do # rubocop:disable Metrics/BlockLength
attr_reader :resource_exception_message
def initialize(backend, name, *args)
@resource_skipped = false
@resource_failed = false
@supports = Inspec::Resource.supports[name]
# attach the backend to this instance
@__backend_runner__ = backend
@__resource_name__ = name
# check resource supports
check_supports unless @supports.nil?
# call the resource initializer
begin
super(*args)
@ -69,6 +79,12 @@ module Inspec
@example = example
end
def check_supports
status = inspec.platform.supported?(@supports)
skip_msg = "Resource #{@__resource_name__.capitalize} is not supported on platform #{inspec.platform.name}/#{inspec.platform.release}."
skip_resource(skip_msg) unless status
end
def skip_resource(message)
@resource_skipped = true
@resource_exception_message = message

View file

@ -136,15 +136,21 @@ module Inspec
# @returns [TrueClass, FalseClass]
#
def supported?
supports_os? && supports_runtime?
supports_platform? && supports_runtime?
end
def supports_os?
metadata.supports_transport?(@backend)
def supports_platform?
if @supports_platform.nil?
@supports_platform = metadata.supports_platform?(@backend)
end
@supports_platform
end
def supports_runtime?
metadata.supports_runtime?
if @supports_runtime.nil?
@supports_runtime = metadata.supports_runtime?
end
@supports_runtime
end
def params

View file

@ -63,10 +63,16 @@ module Inspec
@control_eval_context = nil
end
def profile_supports_os?
def profile_supports_platform?
return true if @conf['profile'].nil?
@conf['profile'].supports_os?
@conf['profile'].supports_platform?
end
def profile_supports_inspec_version?
return true if @conf['profile'].nil?
@conf['profile'].supports_runtime?
end
def remove_rule(id)

View file

@ -16,6 +16,10 @@ module Inspec
@registry ||= default_registry
end
def self.supports
@supports ||= {}
end
def self.new_registry
default_registry.dup
end
@ -131,6 +135,7 @@ require 'resources/packages'
require 'resources/parse_config'
require 'resources/passwd'
require 'resources/pip'
require 'resources/platform'
require 'resources/port'
require 'resources/postgres'
require 'resources/postgres_conf'

View file

@ -185,8 +185,8 @@ module Inspec
"InSpec v#{Inspec::VERSION}.\n"
end
if !profile.supports_os?
raise "This OS/platform (#{@backend.os[:name]}) is not supported by this profile."
if !profile.supports_platform?
raise "This OS/platform (#{@backend.platform.name}/#{@backend.platform.release}) is not supported by this profile."
end
true

View file

@ -2,8 +2,10 @@
# author: Dominik Richter
# author: Christoph Hartmann
require 'resources/platform'
module Inspec::Resources
class OSResource < Inspec.resource(1)
class OSResource < PlatformResource
name 'os'
desc 'Use the os InSpec audit resource to test the platform on which the system is running.'
example "
@ -23,31 +25,17 @@ module Inspec::Resources
# reuse helper methods from backend
%w{aix? redhat? debian? suse? bsd? solaris? linux? unix? windows? hpux? darwin?}.each do |os_family|
define_method(os_family.to_sym) do
inspec.backend.os.send(os_family)
end
end
def [](name)
# convert string to symbol
name = name.to_sym if name.is_a? String
inspec.backend.os[name]
end
# add helper methods for easy access of properties
# allows users to use os.name, os.family, os.release, os.arch
%w{name family release arch}.each do |property|
define_method(property.to_sym) do
inspec.backend.os[property.to_sym]
@platform.send(os_family)
end
end
# helper to collect a hash object easily
def params
{
name: inspec.backend.os[:name],
family: inspec.backend.os[:family],
release: inspec.backend.os[:release],
arch: inspec.backend.os[:arch],
name: @platform[:name],
family: @platform[:family],
release: @platform[:release],
arch: @platform[:arch],
}
end

92
lib/resources/platform.rb Normal file
View file

@ -0,0 +1,92 @@
# encoding: utf-8
module Inspec::Resources
class PlatformResource < Inspec.resource(1)
name 'platform'
desc 'Use the platform InSpec resource to test the platform on which the system is running.'
example "
describe platform do
its('name') { should eq 'redhat' }
end
describe platform do
it { should be_in_family('unix') }
end
"
def initialize
@platform = inspec.backend.os
end
# add helper methods for easy access of properties
%w{name family release arch}.each do |property|
define_method(property.to_sym) do
@platform.send(property)
end
end
def [](name)
# convert string to symbol
name = name.to_sym if name.is_a? String
@platform[name]
end
def platform?(name)
@platform.name == name ||
@platform.family_hierarchy.include?(name)
end
def in_family?(family)
@platform.family_hierarchy.include?(family)
end
def families
@platform.family_hierarchy
end
def supported?(supports)
return true if supports.nil? || supports.empty?
status = true
supports.each do |s|
s.each do |k, v|
# ignore the inspec check for supports
# TODO: remove in inspec 2.0
if k == :inspec
next
elsif %i(os_family os-family platform_family platform-family).include?(k)
status = in_family?(v)
elsif %i(os platform).include?(k)
status = platform?(v)
elsif %i(os_name os-name platform_name platform-name).include?(k)
status = name == v
elsif k == :release
status = check_release(v)
else
status = false
end
break if status == false
end
return true if status == true
end
status
end
def to_s
'Platform Detection'
end
private
def check_release(value)
# allow wild card matching
if value.include?('*')
cleaned = Regexp.escape(value).gsub('\*', '.*?')
!(release =~ /#{cleaned}/).nil?
else
release == value
end
end
end
end

View file

@ -1,2 +1 @@
supports:
- inspec: 0.18
inspec_version: 0.18

View file

@ -1,2 +1 @@
supports:
- inspec: '>= 99.0.0'
inspec_version: '>= 99.0.0'

View file

@ -51,4 +51,35 @@ describe Inspec::Plugins::Resource do
end
end
describe 'supported platform' do
def supports_meta(supports)
Inspec::Resource.supports['os'] = supports
load_resource('os')
end
it 'loads a profile which supports multiple families' do
m = supports_meta([
{ os_family: 'windows' },
{ os_family: 'unix' }
])
m.check_supports.must_be_nil
end
it 'loads a profile which supports multiple names' do
m = supports_meta([
{ os_family: 'windows', os_name: 'windows_2000'},
{ os_family: 'unix', os_name: 'ubuntu' }
])
m.check_supports.must_be_nil
end
it 'reject a profile which supports multiple families' do
m = supports_meta([
{ os_family: 'windows' },
{ os_family: 'redhat' }
])
expect = 'Resource Os is not supported on platform ubuntu/14.04.'
m.check_supports.must_equal expect
end
end
end

View file

@ -101,14 +101,14 @@ describe 'metadata with supported operating systems' do
it 'load a profile with empty supports clause' do
m = supports_meta(nil)
m.supports_transport?(backend).must_equal true
m.supports_platform?(backend).must_equal true
end
it 'supports legacy simple support style, but warns' do
# i.e. setting this to something that would fail:
logger.expect :warn, nil, ["Do not use deprecated `supports: linux` syntax. Instead use:\nsupports:\n - os-family: linux\n\n"]
m = supports_meta('linux')
m.supports_transport?(backend).must_equal true
m.supports_platform?(backend).must_equal true
logger.verify
end
@ -116,53 +116,87 @@ describe 'metadata with supported operating systems' do
# i.e. setting this to something that would fail:
logger.expect :warn, nil, ["Do not use deprecated `supports: linux` syntax. Instead use:\nsupports:\n - os-family: linux\n\n"]
m = supports_meta(['linux'])
m.supports_transport?(backend).must_equal true
m.supports_platform?(backend).must_equal true
logger.verify
end
it 'loads a profile which supports os ubuntu' do
m = supports_meta({ 'os' => 'ubuntu' })
m.supports_transport?(backend).must_equal true
m.supports_platform?(backend).must_equal true
end
it 'loads a profile which supports os name ubuntu' do
m = supports_meta({ 'os-name' => 'ubuntu' })
m.supports_transport?(backend).must_equal true
m.supports_platform?(backend).must_equal true
end
it 'loads a profile which supports os family linux' do
m = supports_meta({ 'os-family' => 'linux' })
m.supports_transport?(backend).must_equal true
m.supports_platform?(backend).must_equal true
end
it 'loads a profile which supports release 14.04' do
m = supports_meta({ 'release' => '14.04' })
m.supports_transport?(backend).must_equal true
m.supports_platform?(backend).must_equal true
end
it 'rejects a profile which supports release 12.04' do
m = supports_meta({ 'release' => '12.04' })
m.supports_transport?(backend).must_equal false
m.supports_platform?(backend).must_equal false
end
it 'loads a profile which supports ubuntu 14.04' do
m = supports_meta({ 'os-name' => 'ubuntu', 'release' => '14.04' })
m.supports_transport?(backend).must_equal true
m.supports_platform?(backend).must_equal true
end
it 'loads a profile which supports ubuntu 14.*' do
m = supports_meta({ 'os-name' => 'ubuntu', 'release' => '14.*' })
m.supports_platform?(backend).must_equal true
end
it 'rejects a profile which supports ubuntu 12.04' do
m = supports_meta({ 'os-name' => 'ubuntu', 'release' => '12.04' })
m.supports_transport?(backend).must_equal false
m.supports_platform?(backend).must_equal false
end
it 'rejects a profile which supports ubuntu 12.*' do
m = supports_meta({ 'os-name' => 'ubuntu', 'release' => '12.*' })
m.supports_platform?(backend).must_equal false
end
it 'loads a profile which supports ubuntu float 14.04 as parsed by yml' do
m = supports_meta({ 'os-name' => 'ubuntu', 'release' => 14.04 })
m.supports_transport?(backend).must_equal true
m.supports_platform?(backend).must_equal true
end
it 'reject unsupported os' do
m = supports_meta({ 'os-name' => 'windows' })
m.supports_transport?(backend).must_equal false
m.supports_platform?(backend).must_equal false
end
it 'loads a profile which supports multiple families' do
m = supports_meta([
{ 'os-family' => 'windows' },
{ 'os-family' => 'unix' }
])
m.supports_platform?(backend).must_equal true
end
it 'loads a profile which supports multiple names' do
m = supports_meta([
{ 'os-family' => 'windows', 'os-name' => 'windows_2000'},
{ 'os-family' => 'unix', 'os-name' => 'ubuntu' }
])
m.supports_platform?(backend).must_equal true
end
it 'reject a profile which supports multiple families' do
m = supports_meta([
{ 'os-family' => 'windows' },
{ 'os-family' => 'redhat' }
])
m.supports_platform?(backend).must_equal false
end
end
@ -170,28 +204,35 @@ describe 'metadata with supported operating systems' do
let(:current_version) { Inspec::VERSION }
let(:next_version) { Gem::Version.new(current_version).bump.to_s }
def version_meta(params)
res = Inspec::Metadata.from_yaml('mock', "---", nil, logger)
res.params[:inspec_version] = params
Inspec::Metadata.finalize(res, 'mock', empty_options, logger)
res
end
it 'returns true on testing the current version' do
m = supports_meta({ 'inspec' => current_version })
m = version_meta(current_version)
m.supports_runtime?.must_equal true
end
it 'returns true on testing the current version' do
m = supports_meta({ 'inspec' => '= ' + current_version })
m = version_meta('= ' + current_version)
m.supports_runtime?.must_equal true
end
it 'returns true on testing >= current version' do
m = supports_meta({ 'inspec' => '>= ' + current_version })
m = version_meta('>= ' + current_version)
m.supports_runtime?.must_equal true
end
it 'returns false on testing >= the next version' do
m = supports_meta({ 'inspec' => '>= ' + next_version })
m = version_meta('>= ' + next_version)
m.supports_runtime?.must_equal false
end
it 'returns false on testing > the next version' do
m = supports_meta({ 'inspec' => '> ' + next_version })
m = version_meta('> ' + next_version)
m.supports_runtime?.must_equal false
end
end

View file

@ -0,0 +1,97 @@
# encoding: utf-8
require 'helper'
require 'inspec/resource'
describe 'Inspec::Resources::Platform' do
let(:resource) { resource = MockLoader.new(:ubuntu1504).load_resource('platform') }
it 'verify platform parsing on Ubuntu' do
_(resource.name).must_equal 'ubuntu'
_(resource.family).must_equal 'debian'
_(resource.release).must_equal '15.04'
_(resource.arch).must_equal 'x86_64'
end
it 'verify platform hash parsing on Ubuntu' do
_(resource[:name]).must_equal 'ubuntu'
_(resource[:family]).must_equal 'debian'
_(resource[:release]).must_equal '15.04'
_(resource[:arch]).must_equal 'x86_64'
end
it 'verify platform families' do
expect = ["debian", "linux", "unix"]
_(resource.families).must_equal expect
end
it 'verify platform? responds correctly' do
_(resource.platform?('windows')).must_equal false
_(resource.platform?('unix')).must_equal true
_(resource.platform?('ubuntu')).must_equal true
_(resource.platform?('mac_os_x')).must_equal false
end
it 'verify family? responds correctly' do
_(resource.in_family?('windows')).must_equal false
_(resource.in_family?('unix')).must_equal true
_(resource.in_family?('ubuntu')).must_equal false
_(resource.in_family?('mac_os_x')).must_equal false
end
it 'verify supported? with multiple families' do
supports = [
{ os_family: 'windows' },
{ os_family: 'unix' }
]
resource.supported?(supports).must_equal true
end
it 'loads a profile which supports multiple names' do
supports = [
{ 'os-family': 'windows', 'os-name': 'windows_2000'},
{ 'os-family': 'unix', 'os-name': 'ubuntu' }
]
resource.supported?(supports).must_equal true
end
it 'reject a profile which supports multiple families' do
supports = [
{ os_family: 'windows' },
{ os_family: 'redhat' }
]
resource.supported?(supports).must_equal false
end
it 'loads a profile which supports release 15.04' do
supports = [
{ 'os-family': 'windows', 'os-name': 'windows_2000'},
{ 'os-name': 'ubuntu', 'release': '15.04'}
]
resource.supported?(supports).must_equal true
end
it 'loads a profile which supports release 15.*' do
supports = [
{ 'os-family': 'windows', 'os-name': 'windows_2000'},
{ 'os-name': 'ubuntu', 'release': '15.*'}
]
resource.supported?(supports).must_equal true
end
it 'loads a profile which supports release *.04' do
supports = [
{ 'os-family': 'windows', 'os-name': 'windows_2000'},
{ 'os-name': 'ubuntu', 'release': '*.04'}
]
resource.supported?(supports).must_equal true
end
it 'reject a profile which supports release 12.*' do
supports = [
{ 'os-family': 'windows', 'os-name': 'windows_2000'},
{ 'os-name': 'ubuntu', 'release': '12.*'}
]
resource.supported?(supports).must_equal false
end
end