diff --git a/lib/resources/service.rb b/lib/resources/service.rb index 7e86c3831..3fcdc0b16 100644 --- a/lib/resources/service.rb +++ b/lib/resources/service.rb @@ -4,13 +4,59 @@ # author: Stephan Renatus # license: All rights reserved -# Usage: -# describe service('dhcp') do -# it { should be_enabled } -# it { should be_installed } -# it { should be_running } -# end -# +class Runlevels < Hash + attr_accessor :owner + + def self.from_hash(owner, hash = {}, filter = nil) + res = Runlevels.new(owner) + filter = filter.first if filter.is_a?(Array) && filter.length <= 1 + + ks = case filter + when nil + hash.keys + when Regexp + hash.keys.find_all { |x| x.to_s =~ filter } + when Array + f = filter.map(&:to_s) + hash.keys.find_all { |x| f.include?(x.to_s) } + when Numeric + hash.keys.include?(filter) ? [filter] : [] + else + hash.keys.find_all { |x| x == filter } + end + + ks.each { |k| res[k] = hash[k] } + res + end + + def initialize(owner, default = false) + @owner = owner + super(default) + end + + def filter(f) + Runlevels.from_hash(owner, self, f) + end + + # Check if all runlevels are enabled + # + # @return [boolean] true if all runlevels are enabled + def enabled? + values.all? + end + + # Check if all runlevels are disabled + # + # @return [boolean] true if all runlevels are disabled + def disabled? + !values.any? + end + + def to_s + "#{owner} runlevels #{keys.join(', ')}" + end +end + # We detect the init system for each operating system, based on the operating # system. # @@ -29,6 +75,10 @@ class Service < Inspec.resource(1) it { should be_enabled } it { should be_running } end + + describe service('service_name').runlevels(3, 5) do + it { should be_enabled } + end " attr_reader :service_ctl @@ -98,7 +148,7 @@ class Service < Inspec.resource(1) @cache ||= @service_mgmt.info(@service_name) end - # verifies the service is enabled + # verifies if the service is enabled def enabled?(_level = nil) return false if info.nil? info[:enabled] @@ -116,6 +166,12 @@ class Service < Inspec.resource(1) info[:running] end + # get all runlevels that are available and their configuration + def runlevels(*args) + return Runlevels.new(self) if info.nil? or info[:runlevels].nil? + Runlevels.from_hash(self, info[:runlevels], args) + end + def to_s "Service #{@service_name}" end @@ -275,6 +331,8 @@ class Upstart < ServiceManager end class SysV < ServiceManager + RUNLEVELS = { 0=>false, 1=>false, 2=>false, 3=>false, 4=>false, 5=>false, 6=>false }.freeze + def initialize(service_name, service_ctl = nil) @service_ctl ||= 'service' super @@ -296,10 +354,15 @@ class SysV < ServiceManager # on rhel via: 'chkconfig --list', is not installed by default # bash: for i in `find /etc/rc*.d -name S*`; do basename $i | sed -r 's/^S[0-9]+//'; done | sort | uniq enabled_services_cmd = inspec.command('find /etc/rc*.d -name S*') - enabled_services = enabled_services_cmd.stdout.split("\n").select { |line| - /(^.*#{service_name}.*)/.match(line) - } - enabled = !enabled_services.empty? + service_line = %r{rc(?[0-6])\.d/S[^/]*?#{Regexp.escape service_name}$} + all_services = enabled_services_cmd.stdout.split("\n").map { |line| + service_line.match(line) + }.compact + enabled = !all_services.empty? + + # Determine a list of runlevels which this service is activated for + runlevels = RUNLEVELS.dup + all_services.each { |x| runlevels[x[:runlevel].to_i] = true } # check if service is really running # service throws an exit code if the service is not installed or @@ -313,6 +376,7 @@ class SysV < ServiceManager installed: true, running: running, enabled: enabled, + runlevels: runlevels, type: 'sysv', } end diff --git a/test/unit/resources/service_test.rb b/test/unit/resources/service_test.rb index 5cbe904f5..ff9ea8a25 100644 --- a/test/unit/resources/service_test.rb +++ b/test/unit/resources/service_test.rb @@ -6,6 +6,7 @@ require 'helper' require 'inspec/resource' describe 'Inspec::Resources::Service' do + let(:runlevels) { {0=>false, 1=>false, 2=>true, 3=>true, 4=>true, 5=>true, 6=>false} } # windows it 'verify service parsing' do @@ -58,7 +59,7 @@ describe 'Inspec::Resources::Service' do # centos 6 with sysv it 'verify centos 6 package parsing' do resource = MockLoader.new(:centos6).load_resource('service', 'sshd') - srv = { name: 'sshd', description: nil, installed: true, running: true, enabled: true, type: 'sysv' } + srv = { name: 'sshd', description: nil, installed: true, running: true, enabled: true, runlevels: runlevels, type: 'sysv' } _(resource.info).must_equal srv _(resource.installed?).must_equal true _(resource.enabled?).must_equal true @@ -67,7 +68,7 @@ describe 'Inspec::Resources::Service' do it 'verify centos 6 package parsing with default sysv_service' do resource = MockLoader.new(:centos6).load_resource('sysv_service', 'sshd') - srv = { name: 'sshd', description: nil, installed: true, running: true, enabled: true, type: 'sysv' } + srv = { name: 'sshd', description: nil, installed: true, running: true, enabled: true, runlevels: runlevels, type: 'sysv' } _(resource.info).must_equal srv _(resource.installed?).must_equal true _(resource.enabled?).must_equal true @@ -125,7 +126,7 @@ describe 'Inspec::Resources::Service' do # debian 7 with systemv it 'verify debian 7 package parsing' do resource = MockLoader.new(:debian7).load_resource('service', 'sshd') - srv = { name: 'sshd', description: nil, installed: true, running: true, enabled: true, type: 'sysv' } + srv = { name: 'sshd', description: nil, installed: true, running: true, enabled: true, runlevels: runlevels, type: 'sysv' } _(resource.info).must_equal srv _(resource.installed?).must_equal true _(resource.enabled?).must_equal true @@ -173,7 +174,7 @@ describe 'Inspec::Resources::Service' do # wrlinux it 'verify wrlinux package parsing' do resource = MockLoader.new(:wrlinux).load_resource('service', 'sshd') - srv = { name: 'sshd', description: nil, installed: true, running: true, enabled: true, type: 'sysv' } + srv = { name: 'sshd', description: nil, installed: true, running: true, enabled: true, runlevels: runlevels, type: 'sysv' } _(resource.info).must_equal srv _(resource.installed?).must_equal true _(resource.enabled?).must_equal true @@ -187,4 +188,45 @@ describe 'Inspec::Resources::Service' do _(resource.installed?).must_equal false _(resource.info).must_equal nil end + + # runlevel detection + describe 'runlevels on centos 6 (system V)' do + let(:service) { MockLoader.new(:centos6).load_resource('service', 'sshd') } + + it 'grabs all runlevels' do + service.runlevels.keys.must_equal [0, 1, 2, 3, 4, 5, 6] + end + + it 'grabs runlevels via filter nil' do + service.runlevels(nil).keys.must_equal [0, 1, 2, 3, 4, 5, 6] + end + + it 'grabs runlevels by number' do + service.runlevels(3).keys.must_equal [3] + end + + it 'grabs runlevels by multiple numbers' do + service.runlevels(3, 4, 8).keys.must_equal [3, 4] + end + + it 'grabs runlevels via regex' do + service.runlevels(/[5-9]/).keys.must_equal [5, 6] + end + + it 'checks enabled true if all services are enabled' do + service.runlevels(2, 4).enabled?.must_equal true + end + + it 'checks enabled false if some services are not enabled' do + service.runlevels(1, 4).enabled?.must_equal false + end + + it 'checks disabled true if all services are disabled' do + service.runlevels(0, 1).disabled?.must_equal true + end + + it 'checks disabled false if some services are not disabled' do + service.runlevels(0, 4).enabled?.must_equal false + end + end end