From 0e410df69d46559f852dc10e4bf7551950037c33 Mon Sep 17 00:00:00 2001 From: Stephan Renatus Date: Wed, 20 Jan 2016 14:33:24 +0100 Subject: [PATCH 01/10] add *_service overrides, allowing for different control binaries --- lib/resources/service.rb | 165 +++++++++++++++++++++++++--- test/unit/resources/service_test.rb | 10 ++ 2 files changed, 159 insertions(+), 16 deletions(-) diff --git a/lib/resources/service.rb b/lib/resources/service.rb index 55fbb20f2..964089cea 100644 --- a/lib/resources/service.rb +++ b/lib/resources/service.rb @@ -34,10 +34,10 @@ class Service < Inspec.resource(1) @service_name = service_name @service_mgmt = nil @cache = nil - select_package_manager + select_service_ctl end - def select_package_manager # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + def select_service_ctl # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity family = inspec.os[:family] case family @@ -117,17 +117,22 @@ class Service < Inspec.resource(1) end class ServiceManager - attr_reader :inspec - def initialize(inspec) + attr_reader :inspec, :service_ctl + def initialize(inspec, service_ctl = nil) @inspec = inspec + @service_ctl = service_ctl end end # @see: http://www.freedesktop.org/software/systemd/man/systemctl.html # @see: http://www.freedesktop.org/software/systemd/man/systemd-system.conf.html class Systemd < ServiceManager + def initialize(inspec, service_ctl = 'systemctl') + super + end + def info(service_name) - cmd = inspec.command("systemctl show --all #{service_name}") + cmd = inspec.command("#{service_ctl} show --all #{service_name}") return nil if cmd.exit_status.to_i != 0 # parse data @@ -206,9 +211,13 @@ end # @see: http://upstart.ubuntu.com class Upstart < ServiceManager + def initialize(service_name, service_ctl = 'initctl') + super + end + def info(service_name) # get the status of upstart service - status = inspec.command("initctl status #{service_name}") + status = inspec.command("#{service_ctl} status #{service_name}") # fallback for systemv services, those are not handled via `initctl` return SysV.new(inspec).info(service_name) if status.exit_status.to_i != 0 @@ -235,7 +244,7 @@ class Upstart < ServiceManager # $ initctl show-config $job | grep -q "^ start on" && echo enabled || echo disabled # Ubuntu 10.04 show-config is not supported # @see http://manpages.ubuntu.com/manpages/maverick/man8/initctl.8.html - config = inspec.command("initctl show-config #{service_name}") + config = inspec.command("#{service_ctl} show-config #{service_name}") enabled = !config.stdout[/^\s*start on/].nil? # implement fallback for Ubuntu 10.04 @@ -251,6 +260,10 @@ class Upstart < ServiceManager end class SysV < ServiceManager + def initialize(service_name, service_ctl = 'service') + super + end + def info(service_name) # check if service is installed # read all available services via ls /etc/init.d/ @@ -270,19 +283,21 @@ class SysV < ServiceManager enabled_services = enabled_services_cmd.stdout.split("\n").select { |line| /(^.*#{service_name}.*)/.match(line) } - enabled_services.empty? ? enabled = false : enabled = true + enabled = !enabled_services.empty? # check if service is really running # service throws an exit code if the service is not installed or # not enabled + # FIXME(sr) # on debian service is located /usr/sbin/service, on centos it is located here /sbin/service - service_cmd = 'service' - service_cmd = '/usr/sbin/service' if inspec.os[:family] == 'debian' - service_cmd = '/sbin/service' if inspec.os[:family] == 'centos' + # service_cmd = 'service' + # service_cmd = '/usr/sbin/service' if inspec.os[:family] == 'debian' + # service_cmd = '/sbin/service' if inspec.os[:family] == 'centos' + # NOTE(sr) It's all in PATH, isn't it? - cmd = inspec.command("#{service_cmd} #{service_name} status") - cmd.exit_status == 0 ? (running = true) : (running = false) + cmd = inspec.command("#{service_ctl} #{service_name} status") + running = cmd.exit_status == 0 { name: service_name, description: nil, @@ -297,6 +312,10 @@ end # @see: https://www.freebsd.org/doc/en/articles/linux-users/startup.html # @see: https://www.freebsd.org/cgi/man.cgi?query=rc.conf&sektion=5 class BSDInit < ServiceManager + def initialize(service_name, service_ctl = 'service') + super + end + def info(service_name) # check if service is enabled # services are enabled in /etc/rc.conf and /etc/defaults/rc.conf @@ -304,7 +323,7 @@ class BSDInit < ServiceManager # service SERVICE status returns the following result if not activated: # Cannot 'status' sshd. Set sshd_enable to YES in /etc/rc.conf or use 'onestatus' instead of 'status'. # gather all enabled services - cmd = inspec.command('service -e') + cmd = inspec.command("#{service_ctl} -e") return nil if cmd.exit_status != 0 # search for the service @@ -314,7 +333,7 @@ class BSDInit < ServiceManager # check if the service is running # if the service is not available or not running, we always get an error code - cmd = inspec.command("service #{service_name} onestatus") + cmd = inspec.command("#{service_ctl} #{service_name} onestatus") cmd.exit_status == 0 ? (running = true) : (running = false) { @@ -331,9 +350,13 @@ end # MacOS / Darwin # new launctl on macos 10.10 class LaunchCtl < ServiceManager + def initialize(service_name, service_ctl = 'launchctl') + super + end + def info(service_name) # get the status of upstart service - cmd = inspec.command('launchctl list') + cmd = inspec.command("#{service_ctl} list") return nil if cmd.exit_status != 0 # search for the service @@ -442,3 +465,113 @@ class WindowsSrv < ServiceManager end end end + +class SystemdService < Service + name 'systemd_service' + desc 'Use the systemd_service InSpec audit resource to test if the named service (controlled by systemd) is installed, running and/or enabled.' + example " + # to override service mgmt auto-detection + describe systemd_service('service_name') do + it { should be_installed } + it { should be_enabled } + it { should be_running } + end + + # to set a non-standard systemctl path + describe systemd_service('service_name', '/path/to/systemctl') do + it { should be_running } + end + " + + def select_service_ctl + @service_mgmt = Systemd.new(inspec) + end +end + +class UpstartService < Service + name 'upstart_service' + desc 'Use the upstart_service InSpec audit resource to test if the named service (controlled by upstart) is installed, running and/or enabled.' + example " + # to override service mgmt auto-detection + describe upstart_service('service_name') do + it { should be_installed } + it { should be_enabled } + it { should be_running } + end + + # to set a non-standard initctl path + describe upstart_service('service_name', '/path/to/initctl') do + it { should be_running } + end + " + + def select_service_ctl + @service_mgmt = Upstart.new(inspec) + end +end + +class SysVService < Service + name 'sysv_service' + desc 'Use the sysv_service InSpec audit resource to test if the named service (controlled by SysV) is installed, running and/or enabled.' + example " + # to override service mgmt auto-detection + describe sysv_service('service_name') do + it { should be_installed } + it { should be_enabled } + it { should be_running } + end + + # to set a non-standard service path + describe sysv_service('service_name', '/path/to/service') do + it { should be_running } + end + " + + def select_service_ctl + @service_mgmt = SysV.new(inspec) + end +end + +class BSDService < Service + name 'bsd_service' + desc 'Use the bsd_service InSpec audit resource to test if the named service (controlled by BSD init) is installed, running and/or enabled.' + example " + # to override service mgmt auto-detection + describe bsd_service('service_name') do + it { should be_installed } + it { should be_enabled } + it { should be_running } + end + + # to set a non-standard service path + describe bsd_service('service_name', '/path/to/service') do + it { should be_running } + end + " + + def select_service_ctl + @service_mgmt = BSDInit.new(inspec) + end +end + +class LaunchdService < Service + name 'launchd_service' + desc 'Use the launchd_service InSpec audit resource to test if the named service (controlled by launchd) is installed, running and/or enabled.' + example " + # to override service mgmt auto-detection + describe launchd_service('service_name') do + it { should be_installed } + it { should be_enabled } + it { should be_running } + end + + # to set a non-standard launchctl path + describe launchd_service('service_name', '/path/to/launchctl') do + it { should be_running } + end + " + + def select_service_ctl + @service_mgmt = LaunchCtl.new(inspec) + end +end diff --git a/test/unit/resources/service_test.rb b/test/unit/resources/service_test.rb index 696d97d7a..d52468793 100644 --- a/test/unit/resources/service_test.rb +++ b/test/unit/resources/service_test.rb @@ -37,6 +37,16 @@ describe 'Inspec::Resources::Service' do _(resource.running?).must_equal true end + # ubuntu 15.04 with systemd_service + it 'verify ubuntu package parsing with systemd_service' do + resource = MockLoader.new(:ubuntu1504).load_resource('systemd_service', 'sshd') + srv = { name: 'sshd.service', description: 'OpenSSH server daemon', installed: true, running: true, enabled: true, type: 'systemd' } + _(resource.info).must_equal srv + _(resource.installed?).must_equal true + _(resource.enabled?).must_equal true + _(resource.running?).must_equal true + end + # centos 6 with systemv it 'verify centos 6 package parsing' do resource = MockLoader.new(:centos6).load_resource('service', 'sshd') From 5c0cdb31c9233e4d7edb6163d49131c5fd83ffcb Mon Sep 17 00:00:00 2001 From: Stephan Renatus Date: Wed, 20 Jan 2016 15:54:36 +0100 Subject: [PATCH 02/10] *_service: add unit tests NB I've just added default duplicates to one instance (i.e., there's only one `systemd_service`), since there's no os-specific magic in them. Also these tests only verify that the default choice is equivalent to `service` on the tested distribution. --- test/unit/resources/service_test.rb | 46 +++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/test/unit/resources/service_test.rb b/test/unit/resources/service_test.rb index d52468793..22806671d 100644 --- a/test/unit/resources/service_test.rb +++ b/test/unit/resources/service_test.rb @@ -27,6 +27,15 @@ describe 'Inspec::Resources::Service' do _(resource.running?).must_equal true end + it 'verify ubuntu package parsing with default upstart_service' do + resource = MockLoader.new(:ubuntu1404).load_resource('upstart_service', 'ssh') + srv = { name: 'ssh', description: nil, installed: true, running: true, enabled: true, type: 'upstart' } + _(resource.info).must_equal srv + _(resource.installed?).must_equal true + _(resource.enabled?).must_equal true + _(resource.running?).must_equal true + end + # ubuntu 15.04 with systemd it 'verify ubuntu package parsing' do resource = MockLoader.new(:ubuntu1504).load_resource('service', 'sshd') @@ -37,8 +46,7 @@ describe 'Inspec::Resources::Service' do _(resource.running?).must_equal true end - # ubuntu 15.04 with systemd_service - it 'verify ubuntu package parsing with systemd_service' do + it 'verify ubuntu package parsing with default systemd_service' do resource = MockLoader.new(:ubuntu1504).load_resource('systemd_service', 'sshd') srv = { name: 'sshd.service', description: 'OpenSSH server daemon', installed: true, running: true, enabled: true, type: 'systemd' } _(resource.info).must_equal srv @@ -47,7 +55,7 @@ describe 'Inspec::Resources::Service' do _(resource.running?).must_equal true end - # centos 6 with systemv + # 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' } @@ -57,6 +65,15 @@ describe 'Inspec::Resources::Service' do _(resource.running?).must_equal true end + 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' } + _(resource.info).must_equal srv + _(resource.installed?).must_equal true + _(resource.enabled?).must_equal true + _(resource.running?).must_equal true + end + # centos 7 with systemd it 'verify centos 7 package parsing' do resource = MockLoader.new(:centos7).load_resource('service', 'sshd') @@ -77,6 +94,15 @@ describe 'Inspec::Resources::Service' do _(resource.running?).must_equal true end + it 'verify freebsd10 package parsing with default bsd_service' do + resource = MockLoader.new(:freebsd10).load_resource('bsd_service', 'sendmail') + srv = { name: 'sendmail', description: nil, installed: true, running: true, enabled: true, type: 'bsd-init' } + _(resource.info).must_equal srv + _(resource.installed?).must_equal true + _(resource.enabled?).must_equal true + _(resource.running?).must_equal true + end + # arch linux with systemd it 'verify arch linux package parsing' do resource = MockLoader.new(:arch).load_resource('service', 'sshd') @@ -98,7 +124,7 @@ describe 'Inspec::Resources::Service' do end # debian 8 with systemd - it 'verify arch linux package parsing' do + it 'verify debian 8 package parsing' do resource = MockLoader.new(:debian8).load_resource('service', 'sshd') srv = { name: 'sshd.service', description: 'OpenSSH server daemon', installed: true, running: true, enabled: true, type: 'systemd' } _(resource.info).must_equal srv @@ -108,7 +134,7 @@ describe 'Inspec::Resources::Service' do end # macos test - it 'verify arch linux package parsing' do + it 'verify mac osx package parsing' do resource = MockLoader.new(:osx104).load_resource('service', 'ssh') srv = { name: 'org.openbsd.ssh-agent', description: nil, installed: true, running: true, enabled: true, type: 'darwin' } _(resource.info).must_equal srv @@ -117,6 +143,15 @@ describe 'Inspec::Resources::Service' do _(resource.running?).must_equal true end + it 'verify mac osx package parsing with default launchd_service' do + resource = MockLoader.new(:osx104).load_resource('launchd_service', 'ssh') + srv = { name: 'org.openbsd.ssh-agent', description: nil, installed: true, running: true, enabled: true, type: 'darwin' } + _(resource.info).must_equal srv + _(resource.installed?).must_equal true + _(resource.enabled?).must_equal true + _(resource.running?).must_equal true + end + # wrlinux it 'verify wrlinux package parsing' do resource = MockLoader.new(:wrlinux).load_resource('service', 'sshd') @@ -127,6 +162,7 @@ describe 'Inspec::Resources::Service' do _(resource.running?).must_equal true end + # unknown OS it 'verify package handling on unsupported os' do resource = MockLoader.new(:undefined).load_resource('service', 'dhcp') From 709e4ca9e00677282e39efe01863f759f7447814 Mon Sep 17 00:00:00 2001 From: Stephan Renatus Date: Wed, 20 Jan 2016 16:05:20 +0100 Subject: [PATCH 03/10] some code simplifications --- lib/resources/service.rb | 39 +++++++++++++-------------------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/lib/resources/service.rb b/lib/resources/service.rb index 964089cea..128dbfe61 100644 --- a/lib/resources/service.rb +++ b/lib/resources/service.rb @@ -143,13 +143,13 @@ class Systemd < ServiceManager ).params # LoadState values eg. loaded, not-found - params['LoadState'] == 'loaded' ? (installed = true) : (installed = false) + installed = params['LoadState'] == 'loaded' # test via 'systemctl is-active service' # SubState values running - params['SubState'] == 'running' ? (running = true) : (running = false) + running = params['SubState'] == 'running' # test via systemctl --quiet is-enabled # ActiveState values eg.g inactive, active - params['UnitFileState'] == 'enabled' ? (enabled = true) : (enabled = false) + enabled = params['UnitFileState'] == 'enabled' { name: params['Id'], @@ -195,17 +195,13 @@ class SrcMstr < ServiceManager # #rubocop:disable Style/TrailingComma def enabled_rc_tcpip? - if inspec.command( - "grep -v ^# /etc/rc.tcpip | grep 'start ' | grep -Eq '(/{0,1}| )#{@name} '", + inspec.command( + "grep -v ^# /etc/rc.tcpip | grep 'start ' | grep -Eq '(/{0,1}| )#{name} '", ).exit_status == 0 - true - else - false - end end def enabled_inittab? - inspec.command("lsitab #{@name}").exit_status.to_i == 0 ? true : false + inspec.command("lsitab #{name}").exit_status == 0 end end @@ -334,7 +330,7 @@ class BSDInit < ServiceManager # check if the service is running # if the service is not available or not running, we always get an error code cmd = inspec.command("#{service_ctl} #{service_name} onestatus") - cmd.exit_status == 0 ? (running = true) : (running = false) + running = cmd.exit_status == 0 { name: service_name, @@ -365,11 +361,11 @@ class LaunchCtl < ServiceManager # extract values from service parsed_srv = /^([0-9]+)\s*(\w*)\s*(\S*)/.match(srv[0]) - !parsed_srv.nil? ? (enabled = true) : (enabled = false) + enabled = !parsed_srv.nil? # check if the service is running pid = parsed_srv[0] - !pid.nil? ? (running = true) : (running = false) + running = !pid.nil? # extract service label srv = parsed_srv[3] || service_name @@ -446,23 +442,14 @@ class WindowsSrv < ServiceManager # 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 + !service['WMI'].nil? && + !service['WMI']['StartMode'].nil? && + service['WMI']['StartMode'] == 'Auto' end # detect if service is running def service_running?(service) - if !service['Service']['Status'].nil? && - service['Service']['Status'] == 4 - true - else - false - end + !service['Service']['Status'].nil? && service['Service']['Status'] == 4 end end From a4e9a34c5d4d7e1e83b7a1b27c804447c8f4b767 Mon Sep 17 00:00:00 2001 From: Stephan Renatus Date: Wed, 20 Jan 2016 17:54:04 +0100 Subject: [PATCH 04/10] add runit_service integration tests --- test/integration/Berksfile | 1 + .../cookbooks/os_prepare/metadata.rb | 1 + .../recipes/_runit_service_centos.rb | 26 +++++++++++++++++ .../cookbooks/os_prepare/recipes/service.rb | 8 ++++-- .../default/sv-default-svlog-run.erb | 3 ++ .../test/integration/default/service_spec.rb | 28 +++++++++++++++++++ 6 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 test/integration/cookbooks/os_prepare/recipes/_runit_service_centos.rb create mode 100644 test/integration/cookbooks/os_prepare/templates/default/sv-default-svlog-run.erb diff --git a/test/integration/Berksfile b/test/integration/Berksfile index 76967d3b7..135c1479e 100644 --- a/test/integration/Berksfile +++ b/test/integration/Berksfile @@ -2,3 +2,4 @@ source 'https://supermarket.chef.io' cookbook 'apt' cookbook 'os_prepare', path: './cookbooks/os_prepare' +cookbook 'runit', github: 'hw-cookbooks/runit' diff --git a/test/integration/cookbooks/os_prepare/metadata.rb b/test/integration/cookbooks/os_prepare/metadata.rb index a874318ef..c19012faa 100644 --- a/test/integration/cookbooks/os_prepare/metadata.rb +++ b/test/integration/cookbooks/os_prepare/metadata.rb @@ -6,3 +6,4 @@ description 'This cookbook prepares the test operating systems' version '1.0.0' depends 'apt' depends 'yum' +depends 'runit' diff --git a/test/integration/cookbooks/os_prepare/recipes/_runit_service_centos.rb b/test/integration/cookbooks/os_prepare/recipes/_runit_service_centos.rb new file mode 100644 index 000000000..191cbabf6 --- /dev/null +++ b/test/integration/cookbooks/os_prepare/recipes/_runit_service_centos.rb @@ -0,0 +1,26 @@ +# encoding: utf-8 +# author: Stephan Renatus + +include_recipe 'runit::default' +package 'socat' + +# put ctl in alt location +directory '/opt/chef/embedded/sbin' do + recursive true + action :create +end + +link '/opt/chef/embedded/sbin/sv' do + to '/sbin/sv' # default location +end + +runit_service 'running-runit-service' do + default_logger true + run_template_name 'default-svlog' +end + +runit_service 'non-running-runit-service' do + default_logger true + run_template_name 'default-svlog' + action :stop +end diff --git a/test/integration/cookbooks/os_prepare/recipes/service.rb b/test/integration/cookbooks/os_prepare/recipes/service.rb index 9fda0529b..795a8e969 100644 --- a/test/integration/cookbooks/os_prepare/recipes/service.rb +++ b/test/integration/cookbooks/os_prepare/recipes/service.rb @@ -4,9 +4,13 @@ # # prepares services -# install ntp as a service on ubuntu case node['platform'] when 'ubuntu' - include_recipe('apt') + # install ntp as a service + include_recipe 'apt::default' package 'ntp' + +when 'centos' + # install runit for alternative service mgmt + include_recipe 'os_prepare::_runit_service_centos' end diff --git a/test/integration/cookbooks/os_prepare/templates/default/sv-default-svlog-run.erb b/test/integration/cookbooks/os_prepare/templates/default/sv-default-svlog-run.erb new file mode 100644 index 000000000..f9768102f --- /dev/null +++ b/test/integration/cookbooks/os_prepare/templates/default/sv-default-svlog-run.erb @@ -0,0 +1,3 @@ +#!/bin/sh +exec 2>&1 +exec socat - TCP4-LISTEN:6701,fork diff --git a/test/integration/test/integration/default/service_spec.rb b/test/integration/test/integration/default/service_spec.rb index 402778c54..297510a29 100644 --- a/test/integration/test/integration/default/service_spec.rb +++ b/test/integration/test/integration/default/service_spec.rb @@ -42,3 +42,31 @@ if os[:family] == 'ubuntu' it { should be_running } end end + +# extra tests for alt. runit on centos with runit_service +if os[:family] == 'centos' + describe runit_service('running-runit-service') do + it { should be_enabled } + it { should be_installed } + it { should be_running } + end + + describe runit_service('non-running-runit-service') do + it { should be_enabled } + it { should be_installed } + it { should_not be_running } + end + + # alt. ctl location + describe runit_service('running-runit-service', '/opt/chef/embedded/sbin/sv') do + it { should be_enabled } + it { should be_installed } + it { should be_running } + end + + describe runit_service('unknown') do + it { should_not be_enabled } + it { should_not be_installed } + it { should_not be_running } + end +end From 3f39b35502f9dac1006a320db2e8040d20259667 Mon Sep 17 00:00:00 2001 From: Stephan Renatus Date: Wed, 20 Jan 2016 17:54:16 +0100 Subject: [PATCH 05/10] add runit_service resource, fix service_ctl handling --- lib/resources/service.rb | 103 +++++++++++++++++++++++++++++---------- 1 file changed, 76 insertions(+), 27 deletions(-) diff --git a/lib/resources/service.rb b/lib/resources/service.rb index 128dbfe61..edba679a9 100644 --- a/lib/resources/service.rb +++ b/lib/resources/service.rb @@ -1,6 +1,7 @@ # encoding: utf-8 # author: Christoph Hartmann # author: Dominik Richter +# author: Stephan Renatus # license: All rights reserved # Usage: @@ -30,14 +31,17 @@ class Service < Inspec.resource(1) end " - def initialize(service_name) + attr_reader :service_ctl + + def initialize(service_name, ctl = nil) @service_name = service_name @service_mgmt = nil + @service_ctl = ctl @cache = nil - select_service_ctl + select_service_mgmt end - def select_service_ctl # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + def select_service_mgmt # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity family = inspec.os[:family] case family @@ -52,34 +56,34 @@ class Service < Inspec.resource(1) when 'ubuntu' version = inspec.os[:release].to_f if version < 15.04 - @service_mgmt = Upstart.new(inspec) + @service_mgmt = Upstart.new(inspec, service_ctl) else - @service_mgmt = Systemd.new(inspec) + @service_mgmt = Systemd.new(inspec, service_ctl) end when 'debian' version = inspec.os[:release].to_i if version > 7 - @service_mgmt = Systemd.new(inspec) + @service_mgmt = Systemd.new(inspec, service_ctl) else - @service_mgmt = SysV.new(inspec) + @service_mgmt = SysV.new(inspec, service_ctl) end when 'redhat', 'fedora', 'centos' version = inspec.os[:release].to_i if (%w{ redhat centos }.include?(family) && version >= 7) || (family == 'fedora' && version >= 15) - @service_mgmt = Systemd.new(inspec) + @service_mgmt = Systemd.new(inspec, service_ctl) else - @service_mgmt = SysV.new(inspec) + @service_mgmt = SysV.new(inspec, service_ctl) end when 'wrlinux' - @service_mgmt = SysV.new(inspec) + @service_mgmt = SysV.new(inspec, service_ctl) when 'darwin' - @service_mgmt = LaunchCtl.new(inspec) + @service_mgmt = LaunchCtl.new(inspec, service_ctl) when 'windows' @service_mgmt = WindowsSrv.new(inspec) when 'freebsd' - @service_mgmt = BSDInit.new(inspec) + @service_mgmt = BSDInit.new(inspec, service_ctl) when 'arch', 'opensuse' - @service_mgmt = Systemd.new(inspec) + @service_mgmt = Systemd.new(inspec, service_ctl) when 'aix' @service_mgmt = SrcMstr.new(inspec) end @@ -343,13 +347,36 @@ class BSDInit < ServiceManager end end -# MacOS / Darwin -# new launctl on macos 10.10 -class LaunchCtl < ServiceManager - def initialize(service_name, service_ctl = 'launchctl') +class Runit < ServiceManager + def initialize(service_name, service_ctl = 'sv') super end + def info(service_name) + # get the status of runit service + cmd = inspec.command("#{service_ctl} status #{service_name}") + # return nil unless cmd.exit_status == 0 # NOTE(sr) why do we do this? + + installed = cmd.exit_status == 0 + puts cmd.stdout + running = installed && !!(cmd.stdout =~ /^run:/) + puts running.inspect + enabled = installed && (running || !!(cmd.stdout =~ /normally up/)) + + { + name: service_name, + description: nil, + installed: installed, + running: running, + enabled: enabled, + type: 'runit', + } + end +end + +# MacOS / Darwin +# new launctl on macos 10.10 +class LaunchCtl < ServiceManager def info(service_name) # get the status of upstart service cmd = inspec.command("#{service_ctl} list") @@ -470,8 +497,8 @@ class SystemdService < Service end " - def select_service_ctl - @service_mgmt = Systemd.new(inspec) + def select_service_mgmt + @service_mgmt = Systemd.new(inspec, service_ctl || 'systemctl') end end @@ -492,8 +519,8 @@ class UpstartService < Service end " - def select_service_ctl - @service_mgmt = Upstart.new(inspec) + def select_service_mgmt + @service_mgmt = Upstart.new(inspec, service_ctl || 'initctl') end end @@ -514,8 +541,8 @@ class SysVService < Service end " - def select_service_ctl - @service_mgmt = SysV.new(inspec) + def select_service_mgmt + @service_mgmt = SysV.new(inspec, service_ctl || 'service') end end @@ -536,8 +563,8 @@ class BSDService < Service end " - def select_service_ctl - @service_mgmt = BSDInit.new(inspec) + def select_service_mgmt + @service_mgmt = BSDInit.new(inspec, service_ctl || 'service') end end @@ -558,7 +585,29 @@ class LaunchdService < Service end " - def select_service_ctl - @service_mgmt = LaunchCtl.new(inspec) + def select_service_mgmt + @service_mgmt = LaunchCtl.new(inspec, service_ctl || 'launchctl') + end +end + +class RunitService < Service + name 'runit_service' + desc 'Use the runit_service InSpec audit resource to test if the named service (controlled by runit) is installed, running and/or enabled.' + example " + # to override service mgmt auto-detection + describe runit_service('service_name') do + it { should be_installed } + it { should be_enabled } + it { should be_running } + end + + # to set a non-standard sv path + describe runit_service('service_name', '/path/to/sv') do + it { should be_running } + end + " + + def select_service_mgmt + @service_mgmt = Runit.new(inspec, service_ctl || 'sv') end end From c761b8b40dd91156e8379f66293529468268ffe3 Mon Sep 17 00:00:00 2001 From: Stephan Renatus Date: Thu, 21 Jan 2016 08:22:04 +0100 Subject: [PATCH 06/10] service resources: further simplifications, debian/centos handling I've recovered the debian/centos special handling of the `service` binary, although I doubt that it's necessary. --- lib/resources/service.rb | 49 +++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/lib/resources/service.rb b/lib/resources/service.rb index edba679a9..a1e498bbd 100644 --- a/lib/resources/service.rb +++ b/lib/resources/service.rb @@ -38,7 +38,9 @@ class Service < Inspec.resource(1) @service_mgmt = nil @service_ctl = ctl @cache = nil - select_service_mgmt + @service_mgmt = select_service_mgmt + + return skip_resource 'The `service` resource is not supported on your OS yet.' if @service_mgmt.nil? end def select_service_mgmt # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity @@ -56,39 +58,37 @@ class Service < Inspec.resource(1) when 'ubuntu' version = inspec.os[:release].to_f if version < 15.04 - @service_mgmt = Upstart.new(inspec, service_ctl) + Upstart.new(inspec, service_ctl) else - @service_mgmt = Systemd.new(inspec, service_ctl) + Systemd.new(inspec, service_ctl) end when 'debian' version = inspec.os[:release].to_i if version > 7 - @service_mgmt = Systemd.new(inspec, service_ctl) + Systemd.new(inspec, service_ctl) else - @service_mgmt = SysV.new(inspec, service_ctl) + SysV.new(inspec, service_ctl || '/usr/sbin/service') end when 'redhat', 'fedora', 'centos' version = inspec.os[:release].to_i if (%w{ redhat centos }.include?(family) && version >= 7) || (family == 'fedora' && version >= 15) - @service_mgmt = Systemd.new(inspec, service_ctl) + Systemd.new(inspec, service_ctl) else - @service_mgmt = SysV.new(inspec, service_ctl) + SysV.new(inspec, service_ctl || '/sbin/service') end when 'wrlinux' - @service_mgmt = SysV.new(inspec, service_ctl) + SysV.new(inspec, service_ctl) when 'darwin' - @service_mgmt = LaunchCtl.new(inspec, service_ctl) + LaunchCtl.new(inspec, service_ctl) when 'windows' - @service_mgmt = WindowsSrv.new(inspec) + WindowsSrv.new(inspec) when 'freebsd' - @service_mgmt = BSDInit.new(inspec, service_ctl) + BSDInit.new(inspec, service_ctl) when 'arch', 'opensuse' - @service_mgmt = Systemd.new(inspec, service_ctl) + Systemd.new(inspec, service_ctl) when 'aix' - @service_mgmt = SrcMstr.new(inspec) + SrcMstr.new(inspec) end - - return skip_resource 'The `service` resource is not supported on your OS yet.' if @service_mgmt.nil? end def info @@ -289,13 +289,6 @@ class SysV < ServiceManager # service throws an exit code if the service is not installed or # not enabled - # FIXME(sr) - # on debian service is located /usr/sbin/service, on centos it is located here /sbin/service - # service_cmd = 'service' - # service_cmd = '/usr/sbin/service' if inspec.os[:family] == 'debian' - # service_cmd = '/sbin/service' if inspec.os[:family] == 'centos' - # NOTE(sr) It's all in PATH, isn't it? - cmd = inspec.command("#{service_ctl} #{service_name} status") running = cmd.exit_status == 0 { @@ -498,7 +491,7 @@ class SystemdService < Service " def select_service_mgmt - @service_mgmt = Systemd.new(inspec, service_ctl || 'systemctl') + Systemd.new(inspec, service_ctl || 'systemctl') end end @@ -520,7 +513,7 @@ class UpstartService < Service " def select_service_mgmt - @service_mgmt = Upstart.new(inspec, service_ctl || 'initctl') + Upstart.new(inspec, service_ctl || 'initctl') end end @@ -542,7 +535,7 @@ class SysVService < Service " def select_service_mgmt - @service_mgmt = SysV.new(inspec, service_ctl || 'service') + SysV.new(inspec, service_ctl || 'service') end end @@ -564,7 +557,7 @@ class BSDService < Service " def select_service_mgmt - @service_mgmt = BSDInit.new(inspec, service_ctl || 'service') + BSDInit.new(inspec, service_ctl || 'service') end end @@ -586,7 +579,7 @@ class LaunchdService < Service " def select_service_mgmt - @service_mgmt = LaunchCtl.new(inspec, service_ctl || 'launchctl') + LaunchCtl.new(inspec, service_ctl || 'launchctl') end end @@ -608,6 +601,6 @@ class RunitService < Service " def select_service_mgmt - @service_mgmt = Runit.new(inspec, service_ctl || 'sv') + Runit.new(inspec, service_ctl || 'sv') end end From 492c7f8146440ffbec98a8707c96e66758e4e50c Mon Sep 17 00:00:00 2001 From: Stephan Renatus Date: Thu, 21 Jan 2016 09:05:29 +0100 Subject: [PATCH 07/10] runit_service: cleanup; fix "non-running-runit-service" test + recipe --- lib/resources/service.rb | 4 +--- .../cookbooks/os_prepare/recipes/_runit_service_centos.rb | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/resources/service.rb b/lib/resources/service.rb index a1e498bbd..86eef58aa 100644 --- a/lib/resources/service.rb +++ b/lib/resources/service.rb @@ -351,10 +351,8 @@ class Runit < ServiceManager # return nil unless cmd.exit_status == 0 # NOTE(sr) why do we do this? installed = cmd.exit_status == 0 - puts cmd.stdout running = installed && !!(cmd.stdout =~ /^run:/) - puts running.inspect - enabled = installed && (running || !!(cmd.stdout =~ /normally up/)) + enabled = installed && (running || !!(cmd.stdout =~ /normally up/) || !!(cmd.stdout =~ /want up/)) { name: service_name, diff --git a/test/integration/cookbooks/os_prepare/recipes/_runit_service_centos.rb b/test/integration/cookbooks/os_prepare/recipes/_runit_service_centos.rb index 191cbabf6..c8fd1ed27 100644 --- a/test/integration/cookbooks/os_prepare/recipes/_runit_service_centos.rb +++ b/test/integration/cookbooks/os_prepare/recipes/_runit_service_centos.rb @@ -22,5 +22,6 @@ end runit_service 'non-running-runit-service' do default_logger true run_template_name 'default-svlog' - action :stop + start_down true + action :create end From f2f2db120dc9626c3199b03779d35da467308bfb Mon Sep 17 00:00:00 2001 From: Stephan Renatus Date: Thu, 21 Jan 2016 09:53:59 +0100 Subject: [PATCH 08/10] fix runit_service tests, replace socat with a proper mock daemon --- .../os_prepare/recipes/_runit_service_centos.rb | 11 +++++++++-- .../templates/default/sv-default-svlog-run.erb | 4 ++-- .../test/integration/default/service_spec.rb | 8 +++++++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/test/integration/cookbooks/os_prepare/recipes/_runit_service_centos.rb b/test/integration/cookbooks/os_prepare/recipes/_runit_service_centos.rb index c8fd1ed27..1188396b1 100644 --- a/test/integration/cookbooks/os_prepare/recipes/_runit_service_centos.rb +++ b/test/integration/cookbooks/os_prepare/recipes/_runit_service_centos.rb @@ -2,7 +2,6 @@ # author: Stephan Renatus include_recipe 'runit::default' -package 'socat' # put ctl in alt location directory '/opt/chef/embedded/sbin' do @@ -19,9 +18,17 @@ runit_service 'running-runit-service' do run_template_name 'default-svlog' end -runit_service 'non-running-runit-service' do +runit_service 'not-enabled-runit-service' do default_logger true run_template_name 'default-svlog' start_down true + action :enable +end + +runit_service 'not-running-runit-service' do + default_logger true + run_template_name 'default-svlog' action :create end + +execute 'sv down not-running-runit-service' diff --git a/test/integration/cookbooks/os_prepare/templates/default/sv-default-svlog-run.erb b/test/integration/cookbooks/os_prepare/templates/default/sv-default-svlog-run.erb index f9768102f..9303e2f28 100644 --- a/test/integration/cookbooks/os_prepare/templates/default/sv-default-svlog-run.erb +++ b/test/integration/cookbooks/os_prepare/templates/default/sv-default-svlog-run.erb @@ -1,3 +1,3 @@ #!/bin/sh -exec 2>&1 -exec socat - TCP4-LISTEN:6701,fork +exec > /dev/null +exec yes diff --git a/test/integration/test/integration/default/service_spec.rb b/test/integration/test/integration/default/service_spec.rb index 297510a29..a617c8121 100644 --- a/test/integration/test/integration/default/service_spec.rb +++ b/test/integration/test/integration/default/service_spec.rb @@ -51,12 +51,18 @@ if os[:family] == 'centos' it { should be_running } end - describe runit_service('non-running-runit-service') do + describe runit_service('not-running-runit-service') do it { should be_enabled } it { should be_installed } it { should_not be_running } end + describe runit_service('not-enabled-runit-service') do + it { should_not be_enabled } + it { should be_installed } + it { should_not be_running } + end + # alt. ctl location describe runit_service('running-runit-service', '/opt/chef/embedded/sbin/sv') do it { should be_enabled } From ef77e01229d49e3d3700401d16373a62a42abde6 Mon Sep 17 00:00:00 2001 From: Stephan Renatus Date: Thu, 21 Jan 2016 11:14:35 +0100 Subject: [PATCH 09/10] service resources: fix service_ctl default/override handling --- lib/resources/service.rb | 47 ++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/lib/resources/service.rb b/lib/resources/service.rb index 86eef58aa..c175fe270 100644 --- a/lib/resources/service.rb +++ b/lib/resources/service.rb @@ -33,10 +33,10 @@ class Service < Inspec.resource(1) attr_reader :service_ctl - def initialize(service_name, ctl = nil) + def initialize(service_name, service_ctl = nil) @service_name = service_name @service_mgmt = nil - @service_ctl = ctl + @service_ctl ||= service_ctl @cache = nil @service_mgmt = select_service_mgmt @@ -92,9 +92,8 @@ class Service < Inspec.resource(1) end def info - return @cache if !@cache.nil? return nil if @service_mgmt.nil? - @cache = @service_mgmt.info(@service_name) + @cache ||= @service_mgmt.info(@service_name) end # verifies the service is enabled @@ -124,15 +123,16 @@ class ServiceManager attr_reader :inspec, :service_ctl def initialize(inspec, service_ctl = nil) @inspec = inspec - @service_ctl = service_ctl + @service_ctl ||= service_ctl end end # @see: http://www.freedesktop.org/software/systemd/man/systemctl.html # @see: http://www.freedesktop.org/software/systemd/man/systemd-system.conf.html class Systemd < ServiceManager - def initialize(inspec, service_ctl = 'systemctl') - super + def initialize(inspec, service_ctl = nil) + @service_ctl ||= 'systemctl' + super end def info(service_name) @@ -211,7 +211,8 @@ end # @see: http://upstart.ubuntu.com class Upstart < ServiceManager - def initialize(service_name, service_ctl = 'initctl') + def initialize(service_name, service_ctl = nil) + @service_ctl ||= 'initctl' super end @@ -260,7 +261,8 @@ class Upstart < ServiceManager end class SysV < ServiceManager - def initialize(service_name, service_ctl = 'service') + def initialize(service_name, service_ctl = nil) + @service_ctl ||= 'service' super end @@ -305,7 +307,8 @@ end # @see: https://www.freebsd.org/doc/en/articles/linux-users/startup.html # @see: https://www.freebsd.org/cgi/man.cgi?query=rc.conf&sektion=5 class BSDInit < ServiceManager - def initialize(service_name, service_ctl = 'service') + def initialize(service_name, service_ctl = nil) + @service_ctl ||= 'service' super end @@ -341,7 +344,8 @@ class BSDInit < ServiceManager end class Runit < ServiceManager - def initialize(service_name, service_ctl = 'sv') + def initialize(service_name, service_ctl = nil) + @service_ctl ||= 'sv' super end @@ -351,8 +355,8 @@ class Runit < ServiceManager # return nil unless cmd.exit_status == 0 # NOTE(sr) why do we do this? installed = cmd.exit_status == 0 - running = installed && !!(cmd.stdout =~ /^run:/) - enabled = installed && (running || !!(cmd.stdout =~ /normally up/) || !!(cmd.stdout =~ /want up/)) + running = installed && (cmd.stdout =~ /^run:/) + enabled = installed && (running || (cmd.stdout =~ /normally up/) || (cmd.stdout =~ /want up/)) { name: service_name, @@ -368,6 +372,11 @@ end # MacOS / Darwin # new launctl on macos 10.10 class LaunchCtl < ServiceManager + def initialize(service_name, service_ctl = nil) + @service_ctl ||= 'launchctl' + super + end + def info(service_name) # get the status of upstart service cmd = inspec.command("#{service_ctl} list") @@ -489,7 +498,7 @@ class SystemdService < Service " def select_service_mgmt - Systemd.new(inspec, service_ctl || 'systemctl') + Systemd.new(inspec, service_ctl) end end @@ -511,7 +520,7 @@ class UpstartService < Service " def select_service_mgmt - Upstart.new(inspec, service_ctl || 'initctl') + Upstart.new(inspec, service_ctl) end end @@ -533,7 +542,7 @@ class SysVService < Service " def select_service_mgmt - SysV.new(inspec, service_ctl || 'service') + SysV.new(inspec, service_ctl) end end @@ -555,7 +564,7 @@ class BSDService < Service " def select_service_mgmt - BSDInit.new(inspec, service_ctl || 'service') + BSDInit.new(inspec, service_ctl) end end @@ -577,7 +586,7 @@ class LaunchdService < Service " def select_service_mgmt - LaunchCtl.new(inspec, service_ctl || 'launchctl') + LaunchCtl.new(inspec, service_ctl) end end @@ -599,6 +608,6 @@ class RunitService < Service " def select_service_mgmt - Runit.new(inspec, service_ctl || 'sv') + Runit.new(inspec, service_ctl) end end From 7a308806aad519aacff34895b3dc1852b8aba9e5 Mon Sep 17 00:00:00 2001 From: Stephan Renatus Date: Thu, 21 Jan 2016 11:41:48 +0100 Subject: [PATCH 10/10] unit tests: add systemd_service instance with overridden service_ctl --- test/helper.rb | 1 + test/unit/resources/service_test.rb | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/test/helper.rb b/test/helper.rb index 96014551f..7a5554aca 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -151,6 +151,7 @@ class MockLoader 'initctl show-config ssh' => cmd.call('initctl-show-config-ssh'), # show ssh service Centos 7 'systemctl show --all sshd' => cmd.call('systemctl-show-all-sshd'), + '/path/to/systemctl show --all sshd' => cmd.call('systemctl-show-all-sshd'), # services on macos 'launchctl list' => cmd.call('launchctl-list'), # services on freebsd 10 diff --git a/test/unit/resources/service_test.rb b/test/unit/resources/service_test.rb index 22806671d..b840508c9 100644 --- a/test/unit/resources/service_test.rb +++ b/test/unit/resources/service_test.rb @@ -84,6 +84,15 @@ describe 'Inspec::Resources::Service' do _(resource.running?).must_equal true end + it 'verify centos 7 package parsing with systemd_service and service_ctl override' do + resource = MockLoader.new(:centos7).load_resource('systemd_service', 'sshd', '/path/to/systemctl') + srv = { name: 'sshd.service', description: 'OpenSSH server daemon', installed: true, running: true, enabled: true, type: 'systemd' } + _(resource.info).must_equal srv + _(resource.installed?).must_equal true + _(resource.enabled?).must_equal true + _(resource.running?).must_equal true + end + # freebsd it 'verify freebsd10 package parsing' do resource = MockLoader.new(:freebsd10).load_resource('service', 'sendmail')