2015-09-14 13:01:33 +00:00
|
|
|
# encoding: utf-8
|
2017-06-28 11:14:19 +00:00
|
|
|
|
2016-05-06 15:38:56 +00:00
|
|
|
require 'hashie'
|
2018-03-22 12:25:45 +00:00
|
|
|
require 'utils/file_reader'
|
2015-09-14 13:01:33 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
module Inspec::Resources
|
|
|
|
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
|
2016-02-14 17:18:40 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
def initialize(owner, default = false)
|
|
|
|
@owner = owner
|
|
|
|
super(default)
|
|
|
|
end
|
2016-02-14 17:18:40 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
def filter(f)
|
|
|
|
Runlevels.from_hash(owner, self, f)
|
|
|
|
end
|
2016-02-14 17:18:40 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# Check if all runlevels are enabled
|
|
|
|
#
|
|
|
|
# @return [boolean] true if all runlevels are enabled
|
|
|
|
def enabled?
|
|
|
|
values.all?
|
|
|
|
end
|
2016-02-14 17:18:40 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# Check if all runlevels are disabled
|
|
|
|
#
|
|
|
|
# @return [boolean] true if all runlevels are disabled
|
|
|
|
def disabled?
|
2017-11-21 07:49:41 +00:00
|
|
|
values.none?
|
2016-03-08 18:06:55 +00:00
|
|
|
end
|
2016-02-14 17:18:40 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
def to_s
|
|
|
|
"#{owner} runlevels #{keys.join(', ')}"
|
|
|
|
end
|
2015-09-26 10:22:37 +00:00
|
|
|
end
|
2015-09-16 21:01:10 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# We detect the init system for each operating system, based on the operating
|
|
|
|
# system.
|
|
|
|
#
|
|
|
|
# Fedora 15 : systemd
|
|
|
|
# RedHat 7 : systemd
|
|
|
|
# Ubuntu 15.04 : systemd
|
|
|
|
# Ubuntu < 15.04 : upstart
|
|
|
|
#
|
|
|
|
# TODO: extend the logic to detect the running init system, independently of OS
|
2017-12-07 19:22:55 +00:00
|
|
|
class Service < Inspec.resource(1)
|
2016-03-08 18:06:55 +00:00
|
|
|
name 'service'
|
2018-02-19 14:26:49 +00:00
|
|
|
supports platform: 'unix'
|
|
|
|
supports platform: 'windows'
|
2016-03-08 18:06:55 +00:00
|
|
|
desc 'Use the service InSpec audit resource to test if the named service is installed, running and/or enabled.'
|
|
|
|
example "
|
|
|
|
describe service('service_name') do
|
|
|
|
it { should be_installed }
|
|
|
|
it { should be_enabled }
|
|
|
|
it { should be_running }
|
2016-05-06 15:38:56 +00:00
|
|
|
its('type') { should be 'systemd' }
|
2016-08-23 20:31:10 +00:00
|
|
|
its ('startmode') { should be 'Auto'}
|
2015-09-15 13:41:30 +00:00
|
|
|
end
|
2016-03-08 18:06:55 +00:00
|
|
|
|
|
|
|
describe service('service_name').runlevels(3, 5) do
|
|
|
|
it { should be_enabled }
|
2015-09-16 20:25:35 +00:00
|
|
|
end
|
2016-05-06 15:38:56 +00:00
|
|
|
|
|
|
|
describe service('service_name').params do
|
|
|
|
its('UnitFileState') { should eq 'enabled' }
|
|
|
|
end
|
2016-03-08 18:06:55 +00:00
|
|
|
"
|
|
|
|
|
|
|
|
attr_reader :service_ctl
|
|
|
|
|
|
|
|
def initialize(service_name, service_ctl = nil)
|
|
|
|
@service_name = service_name
|
|
|
|
@service_mgmt = nil
|
|
|
|
@service_ctl ||= service_ctl
|
|
|
|
@cache = nil
|
|
|
|
@service_mgmt = select_service_mgmt
|
|
|
|
|
|
|
|
return skip_resource 'The `service` resource is not supported on your OS yet.' if @service_mgmt.nil?
|
|
|
|
end
|
|
|
|
|
2016-04-14 17:39:03 +00:00
|
|
|
def select_service_mgmt # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
|
2016-03-08 18:06:55 +00:00
|
|
|
os = inspec.os
|
2016-08-03 17:18:24 +00:00
|
|
|
platform = os[:name]
|
2016-03-08 18:06:55 +00:00
|
|
|
|
|
|
|
# Ubuntu
|
|
|
|
# @see: https://wiki.ubuntu.com/SystemdForUpstartUsers
|
|
|
|
# Ubuntu 15.04 : Systemd
|
|
|
|
# Systemd runs with PID 1 as /sbin/init.
|
|
|
|
# Upstart runs with PID 1 as /sbin/upstart.
|
|
|
|
# Ubuntu < 15.04 : Upstart
|
|
|
|
# Upstart runs with PID 1 as /sbin/init.
|
|
|
|
# Systemd runs with PID 1 as /lib/systemd/systemd.
|
2016-08-03 17:18:24 +00:00
|
|
|
if %w{ubuntu}.include?(platform)
|
|
|
|
version = os[:release].to_f
|
2016-03-08 18:06:55 +00:00
|
|
|
if version < 15.04
|
|
|
|
Upstart.new(inspec, service_ctl)
|
|
|
|
else
|
|
|
|
Systemd.new(inspec, service_ctl)
|
|
|
|
end
|
2016-10-08 21:03:59 +00:00
|
|
|
elsif %w{linuxmint}.include?(platform)
|
|
|
|
version = os[:release].to_f
|
|
|
|
if version < 18
|
|
|
|
Upstart.new(inspec, service_ctl)
|
|
|
|
else
|
|
|
|
Systemd.new(inspec, service_ctl)
|
|
|
|
end
|
2016-08-03 17:18:24 +00:00
|
|
|
elsif %w{debian}.include?(platform)
|
|
|
|
version = os[:release].to_i
|
2016-03-08 18:06:55 +00:00
|
|
|
if version > 7
|
|
|
|
Systemd.new(inspec, service_ctl)
|
|
|
|
else
|
|
|
|
SysV.new(inspec, service_ctl || '/usr/sbin/service')
|
|
|
|
end
|
2018-08-22 19:58:38 +00:00
|
|
|
elsif %w{redhat fedora centos oracle cloudlinux}.include?(platform)
|
2016-08-03 17:18:24 +00:00
|
|
|
version = os[:release].to_i
|
2018-08-22 19:58:38 +00:00
|
|
|
if (%w{redhat centos oracle cloudlinux}.include?(platform) && version >= 7) || (platform == 'fedora' && version >= 15)
|
2016-03-08 18:06:55 +00:00
|
|
|
Systemd.new(inspec, service_ctl)
|
|
|
|
else
|
|
|
|
SysV.new(inspec, service_ctl || '/sbin/service')
|
|
|
|
end
|
2016-08-03 17:18:24 +00:00
|
|
|
elsif %w{wrlinux}.include?(platform)
|
2016-03-08 18:06:55 +00:00
|
|
|
SysV.new(inspec, service_ctl)
|
2016-08-03 17:18:24 +00:00
|
|
|
elsif %w{mac_os_x}.include?(platform)
|
2016-03-08 18:06:55 +00:00
|
|
|
LaunchCtl.new(inspec, service_ctl)
|
|
|
|
elsif os.windows?
|
|
|
|
WindowsSrv.new(inspec)
|
2016-08-03 17:18:24 +00:00
|
|
|
elsif %w{freebsd}.include?(platform)
|
2016-03-08 18:06:55 +00:00
|
|
|
BSDInit.new(inspec, service_ctl)
|
2016-08-03 17:18:24 +00:00
|
|
|
elsif %w{arch}.include?(platform)
|
2016-01-21 07:22:04 +00:00
|
|
|
Systemd.new(inspec, service_ctl)
|
2017-06-15 17:19:58 +00:00
|
|
|
elsif %w{coreos}.include?(platform)
|
|
|
|
Systemd.new(inspec, service_ctl)
|
2016-08-03 17:18:24 +00:00
|
|
|
elsif %w{suse opensuse}.include?(platform)
|
|
|
|
if os[:release].to_i >= 12
|
2016-04-13 22:24:26 +00:00
|
|
|
Systemd.new(inspec, service_ctl)
|
|
|
|
else
|
|
|
|
SysV.new(inspec, service_ctl || '/sbin/service')
|
|
|
|
end
|
2016-08-03 17:18:24 +00:00
|
|
|
elsif %w{aix}.include?(platform)
|
2016-03-08 18:06:55 +00:00
|
|
|
SrcMstr.new(inspec)
|
2016-08-03 17:18:24 +00:00
|
|
|
elsif %w{amazon}.include?(platform)
|
2018-04-19 17:00:39 +00:00
|
|
|
if os[:release] =~ /^20\d\d/
|
2018-04-05 12:35:20 +00:00
|
|
|
Upstart.new(inspec, service_ctl)
|
|
|
|
else
|
|
|
|
Systemd.new(inspec, service_ctl)
|
|
|
|
end
|
2016-03-08 18:06:55 +00:00
|
|
|
elsif os.solaris?
|
|
|
|
Svcs.new(inspec)
|
2015-09-15 13:41:30 +00:00
|
|
|
end
|
2015-09-14 13:01:33 +00:00
|
|
|
end
|
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
def info
|
|
|
|
return nil if @service_mgmt.nil?
|
|
|
|
@cache ||= @service_mgmt.info(@service_name)
|
|
|
|
end
|
2015-09-14 13:01:33 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# verifies if the service is enabled
|
|
|
|
def enabled?(_level = nil)
|
|
|
|
return false if info.nil?
|
|
|
|
info[:enabled]
|
|
|
|
end
|
2015-09-14 13:01:33 +00:00
|
|
|
|
2016-05-06 15:38:56 +00:00
|
|
|
def params
|
|
|
|
return {} if info.nil?
|
|
|
|
Hashie::Mash.new(info[:params] || {})
|
|
|
|
end
|
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# verifies the service is registered
|
|
|
|
def installed?(_name = nil, _version = nil)
|
|
|
|
return false if info.nil?
|
|
|
|
info[:installed]
|
|
|
|
end
|
2015-09-14 13:01:33 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# verifies the service is currently running
|
|
|
|
def running?(_under = nil)
|
|
|
|
return false if info.nil?
|
|
|
|
info[:running]
|
|
|
|
end
|
2015-09-14 13:01:33 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# 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
|
2016-02-14 17:18:40 +00:00
|
|
|
|
2016-05-06 15:38:56 +00:00
|
|
|
# returns the service type from info
|
|
|
|
def type
|
|
|
|
return nil if info.nil?
|
|
|
|
info[:type]
|
|
|
|
end
|
|
|
|
|
|
|
|
# returns the service name from info
|
|
|
|
def name
|
|
|
|
return @service_name if info.nil?
|
|
|
|
info[:name]
|
|
|
|
end
|
|
|
|
|
|
|
|
# returns the service description from info
|
|
|
|
def description
|
|
|
|
return nil if info.nil?
|
|
|
|
info[:description]
|
|
|
|
end
|
|
|
|
|
2016-08-23 20:31:10 +00:00
|
|
|
# returns the service start up mode from info
|
|
|
|
def startmode
|
|
|
|
return nil if info.nil?
|
|
|
|
info[:startmode]
|
|
|
|
end
|
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
def to_s
|
|
|
|
"Service #{@service_name}"
|
|
|
|
end
|
2016-05-06 15:38:56 +00:00
|
|
|
|
|
|
|
private :info
|
2015-09-14 13:01:33 +00:00
|
|
|
end
|
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
class ServiceManager
|
|
|
|
attr_reader :inspec, :service_ctl
|
|
|
|
def initialize(inspec, service_ctl = nil)
|
|
|
|
@inspec = inspec
|
|
|
|
@service_ctl ||= service_ctl
|
|
|
|
end
|
2015-09-14 13:01:33 +00:00
|
|
|
end
|
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# @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 = nil)
|
|
|
|
@service_ctl = service_ctl || 'systemctl'
|
|
|
|
super
|
|
|
|
end
|
2016-01-20 13:33:24 +00:00
|
|
|
|
Use systemctl's helper command to determine enabled & active status
The output of `systemctl show SERVICENAME` can be misleading in the
case of non-native services (i.e. services configured via an init script
and integrated with systemd via a shim) or for more sophisticated unit
types.
For example, the UnitFileState of ntp is "bad":
> systemctl show ntp | grep UnitFileState
UnitFileState=bad
despite systemd reporting it as enabled:
> systemctl is-enabled ntp
ntp.service is not a native service, redirecting to
systemd-sysv-install
Executing /lib/systemd/systemd-sysv-install is-enabled ntp
enabled
Further, the old parsing code would have missed unit files in the
following states that are technically enabled:
enabled-runtime, indirect, generated, and transient
Using the `is-enabled` commands ensures that we report the same enabled
status that systemd reports, without having to update our own parsing in
the event that new unit states are added. Additionally, as shown above,
it handles the sysv compatibility helper.
Similarly, the is-active helper command ensures that we always report
the same active/not-active status as systemd would natively. For
instance, a quick reading of `src/systemctl/systemctl.c` in the systemd
source shows that systemctl reports units as active if they are in the
state `UNIT_ACTIVE` or `UNIT_RELOADING`.
Fixes #749
Signed-off-by: Steven Danna <steve@chef.io>
2016-08-02 10:27:18 +00:00
|
|
|
def is_enabled?(service_name)
|
2018-01-23 20:34:47 +00:00
|
|
|
result = inspec.command("#{service_ctl} is-enabled #{service_name} --quiet")
|
|
|
|
return true if result.exit_status == 0
|
|
|
|
|
|
|
|
# Some systems may not have a `.service` file for a particular service
|
|
|
|
# which causes the `systemctl is-enabled` check to fail despite the
|
|
|
|
# service being enabled. In that event we fallback to `sysv_service`.
|
|
|
|
if result.stderr =~ /Failed to get.*No such file or directory/
|
|
|
|
return inspec.sysv_service(service_name).enabled?
|
|
|
|
end
|
|
|
|
|
|
|
|
false
|
Use systemctl's helper command to determine enabled & active status
The output of `systemctl show SERVICENAME` can be misleading in the
case of non-native services (i.e. services configured via an init script
and integrated with systemd via a shim) or for more sophisticated unit
types.
For example, the UnitFileState of ntp is "bad":
> systemctl show ntp | grep UnitFileState
UnitFileState=bad
despite systemd reporting it as enabled:
> systemctl is-enabled ntp
ntp.service is not a native service, redirecting to
systemd-sysv-install
Executing /lib/systemd/systemd-sysv-install is-enabled ntp
enabled
Further, the old parsing code would have missed unit files in the
following states that are technically enabled:
enabled-runtime, indirect, generated, and transient
Using the `is-enabled` commands ensures that we report the same enabled
status that systemd reports, without having to update our own parsing in
the event that new unit states are added. Additionally, as shown above,
it handles the sysv compatibility helper.
Similarly, the is-active helper command ensures that we always report
the same active/not-active status as systemd would natively. For
instance, a quick reading of `src/systemctl/systemctl.c` in the systemd
source shows that systemctl reports units as active if they are in the
state `UNIT_ACTIVE` or `UNIT_RELOADING`.
Fixes #749
Signed-off-by: Steven Danna <steve@chef.io>
2016-08-02 10:27:18 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def is_active?(service_name)
|
|
|
|
inspec.command("#{service_ctl} is-active #{service_name} --quiet").exit_status == 0
|
|
|
|
end
|
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
def info(service_name)
|
2018-11-13 19:33:08 +00:00
|
|
|
cmd = inspec.command("#{service_ctl} show --no-pager --all #{service_name}")
|
2016-03-08 18:06:55 +00:00
|
|
|
return nil if cmd.exit_status.to_i != 0
|
|
|
|
|
|
|
|
# parse data
|
|
|
|
params = SimpleConfig.new(
|
|
|
|
cmd.stdout.chomp,
|
2017-04-26 21:18:14 +00:00
|
|
|
assignment_regex: /^\s*([^=]*?)\s*=\s*(.*?)\s*$/,
|
2016-03-08 18:06:55 +00:00
|
|
|
multiple_values: false,
|
|
|
|
).params
|
|
|
|
|
|
|
|
# LoadState values eg. loaded, not-found
|
|
|
|
installed = params['LoadState'] == 'loaded'
|
|
|
|
|
|
|
|
{
|
|
|
|
name: params['Id'],
|
|
|
|
description: params['Description'],
|
|
|
|
installed: installed,
|
Use systemctl's helper command to determine enabled & active status
The output of `systemctl show SERVICENAME` can be misleading in the
case of non-native services (i.e. services configured via an init script
and integrated with systemd via a shim) or for more sophisticated unit
types.
For example, the UnitFileState of ntp is "bad":
> systemctl show ntp | grep UnitFileState
UnitFileState=bad
despite systemd reporting it as enabled:
> systemctl is-enabled ntp
ntp.service is not a native service, redirecting to
systemd-sysv-install
Executing /lib/systemd/systemd-sysv-install is-enabled ntp
enabled
Further, the old parsing code would have missed unit files in the
following states that are technically enabled:
enabled-runtime, indirect, generated, and transient
Using the `is-enabled` commands ensures that we report the same enabled
status that systemd reports, without having to update our own parsing in
the event that new unit states are added. Additionally, as shown above,
it handles the sysv compatibility helper.
Similarly, the is-active helper command ensures that we always report
the same active/not-active status as systemd would natively. For
instance, a quick reading of `src/systemctl/systemctl.c` in the systemd
source shows that systemctl reports units as active if they are in the
state `UNIT_ACTIVE` or `UNIT_RELOADING`.
Fixes #749
Signed-off-by: Steven Danna <steve@chef.io>
2016-08-02 10:27:18 +00:00
|
|
|
running: is_active?(service_name),
|
|
|
|
enabled: is_enabled?(service_name),
|
2016-03-08 18:06:55 +00:00
|
|
|
type: 'systemd',
|
2016-05-06 15:38:56 +00:00
|
|
|
params: params,
|
2016-03-08 18:06:55 +00:00
|
|
|
}
|
|
|
|
end
|
2015-09-15 13:39:34 +00:00
|
|
|
end
|
2015-09-15 13:40:14 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# AIX services
|
|
|
|
class SrcMstr < ServiceManager
|
|
|
|
attr_reader :name
|
2015-12-23 19:22:31 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
def info(service_name)
|
|
|
|
@name = service_name
|
|
|
|
running = status?
|
|
|
|
return nil if running.nil?
|
2016-01-28 13:25:49 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
{
|
|
|
|
name: service_name,
|
|
|
|
description: nil,
|
|
|
|
installed: true,
|
|
|
|
running: running,
|
|
|
|
enabled: enabled?,
|
|
|
|
type: 'srcmstr',
|
|
|
|
}
|
|
|
|
end
|
2015-12-23 19:22:31 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
private
|
2015-12-23 19:22:31 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
def status?
|
|
|
|
status_cmd = inspec.command("lssrc -s #{@name}")
|
|
|
|
return nil if status_cmd.exit_status.to_i != 0
|
|
|
|
status_cmd.stdout.split(/\n/).last.chomp =~ /active$/ ? true : false
|
|
|
|
end
|
2015-12-23 19:22:31 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
def enabled?
|
|
|
|
enabled_rc_tcpip? || enabled_inittab?
|
|
|
|
end
|
2015-12-23 19:22:31 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
def enabled_rc_tcpip?
|
|
|
|
inspec.command(
|
|
|
|
"grep -v ^# /etc/rc.tcpip | grep 'start ' | grep -Eq '(/{0,1}| )#{name} '",
|
|
|
|
).exit_status == 0
|
|
|
|
end
|
2016-01-20 13:33:24 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
def enabled_inittab?
|
|
|
|
inspec.command("lsitab #{name}").exit_status == 0
|
|
|
|
end
|
2015-11-13 09:30:37 +00:00
|
|
|
end
|
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# @see: http://upstart.ubuntu.com
|
|
|
|
class Upstart < ServiceManager
|
2018-03-22 12:25:45 +00:00
|
|
|
include FileReader
|
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
def initialize(service_name, service_ctl = nil)
|
|
|
|
@service_ctl = service_ctl || 'initctl'
|
|
|
|
super
|
2016-02-05 08:25:34 +00:00
|
|
|
end
|
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
def info(service_name)
|
|
|
|
# get the status of upstart service
|
|
|
|
status = inspec.command("#{service_ctl} status #{service_name}")
|
|
|
|
|
|
|
|
# fallback for systemv services, those are not handled via `initctl`
|
2017-03-08 20:51:04 +00:00
|
|
|
return SysV.new(inspec).info(service_name) if status.exit_status.to_i != 0 || status.stdout == ''
|
2016-03-08 18:06:55 +00:00
|
|
|
|
|
|
|
# @see: http://upstart.ubuntu.com/cookbook/#job-states
|
|
|
|
# grep for running to indicate the service is there
|
|
|
|
running = !status.stdout[%r{start/running}].nil?
|
2018-03-22 12:25:45 +00:00
|
|
|
enabled = info_enabled(service_name)
|
2016-03-08 18:06:55 +00:00
|
|
|
|
|
|
|
{
|
|
|
|
name: service_name,
|
|
|
|
description: nil,
|
|
|
|
installed: true,
|
|
|
|
running: running,
|
2018-03-22 12:25:45 +00:00
|
|
|
enabled: enabled,
|
2016-03-08 18:06:55 +00:00
|
|
|
type: 'upstart',
|
|
|
|
}
|
2015-11-09 23:36:34 +00:00
|
|
|
end
|
2015-11-13 09:30:37 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
private
|
2016-02-05 08:25:34 +00:00
|
|
|
|
2017-09-21 16:21:34 +00:00
|
|
|
def info_enabled(service_name)
|
2016-03-08 18:06:55 +00:00
|
|
|
# check if a service is enabled
|
2018-03-22 12:25:45 +00:00
|
|
|
config = read_file_content("/etc/init/#{service_name}.conf", allow_empty: true)
|
2016-01-20 13:33:24 +00:00
|
|
|
|
2017-09-21 16:21:34 +00:00
|
|
|
!config.match(/^\s*start on/).nil?
|
2016-03-08 18:06:55 +00:00
|
|
|
end
|
2015-09-18 06:50:55 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
def version
|
2017-11-21 07:49:41 +00:00
|
|
|
@version ||= begin
|
2016-03-08 18:06:55 +00:00
|
|
|
out = inspec.command("#{service_ctl} --version").stdout
|
|
|
|
Gem::Version.new(out[/\(upstart ([^\)]+)\)/, 1])
|
2017-11-21 07:49:41 +00:00
|
|
|
end
|
2016-03-08 18:06:55 +00:00
|
|
|
end
|
2016-01-20 13:33:24 +00:00
|
|
|
end
|
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
class SysV < ServiceManager
|
|
|
|
RUNLEVELS = { 0=>false, 1=>false, 2=>false, 3=>false, 4=>false, 5=>false, 6=>false }.freeze
|
2016-01-20 16:54:16 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
def initialize(service_name, service_ctl = nil)
|
|
|
|
@service_ctl = service_ctl || 'service'
|
|
|
|
super
|
|
|
|
end
|
2016-01-21 10:14:35 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
def info(service_name)
|
|
|
|
# check if service is installed
|
|
|
|
# read all available services via ls /etc/init.d/
|
|
|
|
srvlist = inspec.command('ls -1 /etc/init.d/')
|
|
|
|
return nil if srvlist.exit_status != 0
|
|
|
|
|
|
|
|
# check if the service is in list
|
|
|
|
service = srvlist.stdout.split("\n").select { |srv| srv == service_name }
|
|
|
|
|
|
|
|
# abort if we could not find any service
|
|
|
|
return nil if service.empty?
|
|
|
|
|
|
|
|
# read all enabled services from runlevel
|
|
|
|
# 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
|
2017-11-02 14:03:51 +00:00
|
|
|
enabled_services_cmd = inspec.command('find /etc/rc*.d /etc/init.d/rc*.d -name "S*"').stdout
|
2016-03-08 18:06:55 +00:00
|
|
|
service_line = %r{rc(?<runlevel>[0-6])\.d/S[^/]*?#{Regexp.escape service_name}$}
|
2016-09-01 16:26:23 +00:00
|
|
|
all_services = enabled_services_cmd.split("\n").map { |line|
|
2016-03-08 18:06:55 +00:00
|
|
|
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
|
|
|
|
# not enabled
|
|
|
|
|
|
|
|
cmd = inspec.command("#{service_ctl} #{service_name} status")
|
|
|
|
running = cmd.exit_status == 0
|
|
|
|
{
|
|
|
|
name: service_name,
|
|
|
|
description: nil,
|
|
|
|
installed: true,
|
|
|
|
running: running,
|
|
|
|
enabled: enabled,
|
|
|
|
runlevels: runlevels,
|
|
|
|
type: 'sysv',
|
|
|
|
}
|
|
|
|
end
|
2015-09-16 09:34:20 +00:00
|
|
|
end
|
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# @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 = nil)
|
|
|
|
@service_ctl = service_ctl || 'service'
|
|
|
|
super
|
|
|
|
end
|
2015-09-26 10:22:37 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
def info(service_name)
|
|
|
|
# check if service is enabled
|
|
|
|
# services are enabled in /etc/rc.conf and /etc/defaults/rc.conf
|
|
|
|
# via #{service_name}_enable="YES"
|
|
|
|
# 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_ctl} -e")
|
|
|
|
return nil if cmd.exit_status != 0
|
|
|
|
|
|
|
|
# search for the service
|
|
|
|
srv = /(^.*#{service_name}$)/.match(cmd.stdout)
|
|
|
|
return nil if srv.nil? || srv[0].nil?
|
|
|
|
enabled = true
|
2015-09-26 10:22:37 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# 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")
|
|
|
|
running = cmd.exit_status == 0
|
|
|
|
|
|
|
|
{
|
|
|
|
name: service_name,
|
|
|
|
description: nil,
|
|
|
|
installed: true,
|
|
|
|
running: running,
|
|
|
|
enabled: enabled,
|
|
|
|
type: 'bsd-init',
|
|
|
|
}
|
|
|
|
end
|
2015-09-26 10:22:37 +00:00
|
|
|
end
|
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
class Runit < ServiceManager
|
|
|
|
def initialize(service_name, service_ctl = nil)
|
|
|
|
@service_ctl = service_ctl || 'sv'
|
|
|
|
super
|
|
|
|
end
|
2016-01-20 13:33:24 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# rubocop:disable Style/DoubleNegation
|
|
|
|
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
|
|
|
|
running = installed && !!(cmd.stdout =~ /^run:/)
|
|
|
|
enabled = installed && (running || !!(cmd.stdout =~ /normally up/) || !!(cmd.stdout =~ /want up/))
|
|
|
|
|
|
|
|
{
|
|
|
|
name: service_name,
|
|
|
|
description: nil,
|
|
|
|
installed: installed,
|
|
|
|
running: running,
|
|
|
|
enabled: enabled,
|
|
|
|
type: 'runit',
|
|
|
|
}
|
|
|
|
end
|
2016-01-28 13:25:49 +00:00
|
|
|
end
|
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# MacOS / Darwin
|
|
|
|
# new launctl on macos 10.10
|
|
|
|
class LaunchCtl < ServiceManager
|
|
|
|
def initialize(service_name, service_ctl = nil)
|
|
|
|
@service_ctl = service_ctl || 'launchctl'
|
|
|
|
super
|
|
|
|
end
|
|
|
|
|
|
|
|
def info(service_name)
|
|
|
|
# get the status of upstart service
|
|
|
|
cmd = inspec.command("#{service_ctl} list")
|
|
|
|
return nil if cmd.exit_status != 0
|
|
|
|
|
|
|
|
# search for the service
|
|
|
|
srv = /(^.*#{service_name}.*)/.match(cmd.stdout)
|
|
|
|
return nil if srv.nil? || srv[0].nil?
|
|
|
|
|
|
|
|
# extract values from service
|
|
|
|
parsed_srv = /^(?<pid>[0-9-]+)\t(?<exit>[0-9]+)\t(?<name>\S*)$/.match(srv[0])
|
|
|
|
enabled = !parsed_srv['name'].nil? # it's in the list
|
|
|
|
|
|
|
|
# check if the service is running
|
|
|
|
pid = parsed_srv['pid']
|
|
|
|
running = pid != '-'
|
|
|
|
|
|
|
|
# extract service label
|
|
|
|
srv = parsed_srv['name'] || service_name
|
|
|
|
|
|
|
|
{
|
|
|
|
name: srv,
|
|
|
|
description: nil,
|
|
|
|
installed: true,
|
|
|
|
running: running,
|
|
|
|
enabled: enabled,
|
|
|
|
type: 'darwin',
|
|
|
|
}
|
|
|
|
end
|
2016-01-28 13:25:49 +00:00
|
|
|
end
|
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# Determine the service state from Windows
|
|
|
|
# Uses Powershell to retrieve the information
|
|
|
|
class WindowsSrv < ServiceManager
|
|
|
|
# Determine service details
|
|
|
|
# PS: Get-Service -Name 'dhcp'| Select-Object -Property Name, DisplayName, Status | ConvertTo-Json
|
|
|
|
# {
|
|
|
|
# "Name": "dhcp",
|
|
|
|
# "DisplayName": "DHCP Client",
|
|
|
|
# "Status": 4
|
|
|
|
# }
|
|
|
|
#
|
|
|
|
# Until StartMode is not added to Get-Service, we need to do a workaround
|
|
|
|
# @see: https://connect.microsoft.com/PowerShell/feedback/details/424948/i-would-like-to-see-the-property-starttype-added-to-get-services
|
2016-08-23 20:31:10 +00:00
|
|
|
# Also see: https://msdn.microsoft.com/en-us/library/aa384896(v=vs.85).aspx
|
2016-03-08 18:06:55 +00:00
|
|
|
# Use the following powershell to determine the start mode
|
|
|
|
# PS: Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq $name -or $_.DisplayName -eq $name} | Select-Object -Prop
|
|
|
|
# erty Name, StartMode, State, Status | ConvertTo-Json
|
|
|
|
# {
|
|
|
|
# "Name": "Dhcp",
|
|
|
|
# "StartMode": "Auto",
|
|
|
|
# "State": "Running",
|
|
|
|
# "Status": "OK"
|
|
|
|
# }
|
|
|
|
#
|
|
|
|
# Windows Services have the following status code:
|
|
|
|
# @see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms685996(v=vs.85).aspx
|
|
|
|
# - 1: Stopped
|
|
|
|
# - 2: Starting
|
|
|
|
# - 3: Stopping
|
|
|
|
# - 4: Running
|
|
|
|
# - 5: Continue Pending
|
|
|
|
# - 6: Pause Pending
|
|
|
|
# - 7: Paused
|
|
|
|
def info(service_name)
|
2016-08-31 14:01:07 +00:00
|
|
|
cmd = inspec.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")
|
2016-03-08 18:06:55 +00:00
|
|
|
|
|
|
|
# cannot rely on exit code for now, successful command returns exit code 1
|
|
|
|
# return nil if cmd.exit_status != 0
|
|
|
|
# try to parse json
|
|
|
|
begin
|
|
|
|
service = JSON.parse(cmd.stdout)
|
|
|
|
rescue JSON::ParserError => _e
|
|
|
|
return nil
|
|
|
|
end
|
2016-01-28 13:25:49 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# check that we got a response
|
|
|
|
return nil if service.nil? || service['Service'].nil?
|
|
|
|
|
|
|
|
{
|
|
|
|
name: service['Service']['Name'],
|
|
|
|
description: service['Service']['DisplayName'],
|
|
|
|
installed: true,
|
|
|
|
running: service_running?(service),
|
|
|
|
enabled: service_enabled?(service),
|
2016-08-23 20:31:10 +00:00
|
|
|
startmode: service['WMI']['StartMode'],
|
2016-03-08 18:06:55 +00:00
|
|
|
type: 'windows',
|
|
|
|
}
|
2016-01-20 13:33:24 +00:00
|
|
|
end
|
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
private
|
|
|
|
|
|
|
|
# detect if service is enabled
|
|
|
|
def service_enabled?(service)
|
|
|
|
!service['WMI'].nil? &&
|
|
|
|
!service['WMI']['StartMode'].nil? &&
|
2016-11-03 16:07:21 +00:00
|
|
|
(service['WMI']['StartMode'] == 'Auto' ||
|
|
|
|
service['WMI']['StartMode'] == 'Manual')
|
2016-01-20 13:33:24 +00:00
|
|
|
end
|
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# detect if service is running
|
|
|
|
def service_running?(service)
|
|
|
|
!service['Service']['Status'].nil? && service['Service']['Status'] == 4
|
|
|
|
end
|
2016-01-20 13:33:24 +00:00
|
|
|
end
|
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# Solaris services
|
|
|
|
class Svcs < ServiceManager
|
|
|
|
def initialize(service_name, service_ctl = nil)
|
|
|
|
@service_ctl = service_ctl || 'svcs'
|
|
|
|
super
|
2016-01-20 13:33:24 +00:00
|
|
|
end
|
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
def info(service_name)
|
|
|
|
# get the status of runit service
|
|
|
|
cmd = inspec.command("#{service_ctl} -l #{service_name}")
|
|
|
|
return nil if cmd.exit_status != 0
|
|
|
|
|
|
|
|
params = SimpleConfig.new(
|
|
|
|
cmd.stdout.chomp,
|
2017-04-26 21:18:14 +00:00
|
|
|
assignment_regex: /^(\w+)\s*(.*)$/,
|
2016-03-08 18:06:55 +00:00
|
|
|
multiple_values: false,
|
|
|
|
).params
|
|
|
|
|
|
|
|
installed = cmd.exit_status == 0
|
|
|
|
running = installed && (params['state'] == 'online')
|
|
|
|
enabled = installed && (params['enabled'] == 'true')
|
|
|
|
|
|
|
|
{
|
|
|
|
name: service_name,
|
|
|
|
description: params['name'],
|
|
|
|
installed: installed,
|
|
|
|
running: running,
|
|
|
|
enabled: enabled,
|
|
|
|
type: 'svcs',
|
|
|
|
}
|
2016-01-20 13:33:24 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# specific resources for specific service managers
|
2016-01-20 13:33:24 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
class SystemdService < Service
|
|
|
|
name 'systemd_service'
|
2018-02-19 14:26:49 +00:00
|
|
|
supports platform: 'unix'
|
2016-03-08 18:06:55 +00:00
|
|
|
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
|
2016-01-20 13:33:24 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# to set a non-standard systemctl path
|
|
|
|
describe systemd_service('service_name', '/path/to/systemctl') do
|
|
|
|
it { should be_running }
|
|
|
|
end
|
|
|
|
"
|
2016-01-20 13:33:24 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
def select_service_mgmt
|
|
|
|
Systemd.new(inspec, service_ctl)
|
2016-01-20 13:33:24 +00:00
|
|
|
end
|
2016-03-08 18:06:55 +00:00
|
|
|
end
|
2016-01-20 13:33:24 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
class UpstartService < Service
|
|
|
|
name 'upstart_service'
|
2018-02-19 14:26:49 +00:00
|
|
|
supports platform: 'unix'
|
2016-03-08 18:06:55 +00:00
|
|
|
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
|
2016-01-20 13:33:24 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# to set a non-standard initctl path
|
|
|
|
describe upstart_service('service_name', '/path/to/initctl') do
|
|
|
|
it { should be_running }
|
|
|
|
end
|
|
|
|
"
|
2016-01-20 13:33:24 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
def select_service_mgmt
|
|
|
|
Upstart.new(inspec, service_ctl)
|
2016-01-20 13:33:24 +00:00
|
|
|
end
|
2016-03-08 18:06:55 +00:00
|
|
|
end
|
2016-01-20 13:33:24 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
class SysVService < Service
|
|
|
|
name 'sysv_service'
|
2018-02-19 14:26:49 +00:00
|
|
|
supports platform: 'unix'
|
2016-03-08 18:06:55 +00:00
|
|
|
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
|
2016-01-20 13:33:24 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# to set a non-standard service path
|
|
|
|
describe sysv_service('service_name', '/path/to/service') do
|
|
|
|
it { should be_running }
|
|
|
|
end
|
|
|
|
"
|
|
|
|
|
|
|
|
def select_service_mgmt
|
|
|
|
SysV.new(inspec, service_ctl)
|
|
|
|
end
|
2016-01-20 16:54:16 +00:00
|
|
|
end
|
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
class BSDService < Service
|
|
|
|
name 'bsd_service'
|
2018-02-19 14:26:49 +00:00
|
|
|
supports platform: 'unix'
|
2016-03-08 18:06:55 +00:00
|
|
|
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_mgmt
|
|
|
|
BSDInit.new(inspec, service_ctl)
|
2016-01-20 16:54:16 +00:00
|
|
|
end
|
2016-03-08 18:06:55 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
class LaunchdService < Service
|
|
|
|
name 'launchd_service'
|
2018-02-19 14:26:49 +00:00
|
|
|
supports platform: 'unix'
|
2016-03-08 18:06:55 +00:00
|
|
|
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
|
2016-01-20 16:54:16 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# to set a non-standard launchctl path
|
|
|
|
describe launchd_service('service_name', '/path/to/launchctl') do
|
|
|
|
it { should be_running }
|
|
|
|
end
|
|
|
|
"
|
|
|
|
|
|
|
|
def select_service_mgmt
|
|
|
|
LaunchCtl.new(inspec, service_ctl)
|
2016-01-20 16:54:16 +00:00
|
|
|
end
|
2016-03-08 18:06:55 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
class RunitService < Service
|
|
|
|
name 'runit_service'
|
2018-02-19 14:26:49 +00:00
|
|
|
supports platform: 'unix'
|
2016-03-08 18:06:55 +00:00
|
|
|
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
|
|
|
|
"
|
2016-01-20 16:54:16 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
def select_service_mgmt
|
|
|
|
Runit.new(inspec, service_ctl)
|
|
|
|
end
|
2016-01-20 13:33:24 +00:00
|
|
|
end
|
|
|
|
end
|