Class: Potluck::Service
- Inherits:
-
Object
- Object
- Potluck::Service
- Defined in:
- lib/potluck/service.rb
Overview
A Ruby interface for configuring, controlling, and interacting with external processes. Serves as a parent class for service-specific child classes.
Constant Summary collapse
- SERVICE_PREFIX =
'potluck.npickens.'
- LAUNCHCTL_ERROR_REGEX =
/^-|\t[^0]\t/.freeze
Class Method Summary collapse
-
.ensure_launchctl! ⇒ Object
Checks if launchctl is available and raises an error if not.
-
.launchctl? ⇒ Boolean
Returns true if launchctl is available.
-
.launchctl_name ⇒ Object
Name for the launchctl service.
-
.plist(content = '') ⇒ Object
Content of the launchctl plist file.
-
.plist_path ⇒ Object
Path to the launchctl plist file of the service.
-
.pretty_name ⇒ Object
Human-friendly name of the service.
-
.service_name ⇒ Object
Computer-friendly name of the service.
-
.write_plist ⇒ Object
Writes the service’s launchctl plist file to disk.
Instance Method Summary collapse
-
#initialize(logger: nil, manage: self.class.launchctl?) ⇒ Service
constructor
Creates a new instance.
-
#log(message, error = false) ⇒ Object
Logs a message using the logger or stdout/stderr if no logger is configured.
-
#manage? ⇒ Boolean
Returns true if the service is managed.
-
#manage_with_launchctl? ⇒ Boolean
Returns true if the service is managed via launchctl.
-
#restart ⇒ Object
Restarts the service if it’s managed by calling stop and then start.
-
#run(command, capture_stderr: true) ⇒ Object
Runs a command with the default shell.
-
#start ⇒ Object
Starts the service if it’s managed and is not active.
-
#status ⇒ Object
Returns the status of the service:.
-
#stop ⇒ Object
Stops the service if it’s managed and is active or in an error state.
Constructor Details
#initialize(logger: nil, manage: self.class.launchctl?) ⇒ Service
Creates a new instance.
-
logger
-Logger
instance to use for outputting info and error messages (optional). Output will be sent to stdout and stderr if none is supplied. -
manage
- True if the service runs locally and should be managed by this process (default: true if launchctl is available and false otherwise).
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/potluck/service.rb', line 27 def initialize(logger: nil, manage: self.class.launchctl?) @logger = logger @manage = !!manage @manage_with_launchctl = false if manage.kind_of?(Hash) @status_command = manage[:status] @status_error_regex = manage[:status_error_regex] @start_command = manage[:start] @stop_command = manage[:stop] elsif manage @manage_with_launchctl = true self.class.ensure_launchctl! end end |
Class Method Details
.ensure_launchctl! ⇒ Object
Checks if launchctl is available and raises an error if not.
224 225 226 |
# File 'lib/potluck/service.rb', line 224 def self.ensure_launchctl! launchctl? || raise(ServiceError, "Cannot manage #{pretty_name}: launchctl not found") end |
.launchctl? ⇒ Boolean
Returns true if launchctl is available.
217 218 219 |
# File 'lib/potluck/service.rb', line 217 def self.launchctl? defined?(@@launchctl) ? @@launchctl : (@@launchctl = `which launchctl 2>&1` && $? == 0) end |
.launchctl_name ⇒ Object
Name for the launchctl service.
173 174 175 |
# File 'lib/potluck/service.rb', line 173 def self.launchctl_name "#{SERVICE_PREFIX}#{service_name}" end |
.plist(content = '') ⇒ Object
Content of the launchctl plist file.
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/potluck/service.rb', line 187 def self.plist(content = '') <<~EOS <?xml version="1.0" encoding="UTF-8"?> #{'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.'\ '0.dtd">'} <plist version="1.0"> <dict> <key>Label</key> <string>#{launchctl_name}</string> <key>RunAtLoad</key> <true/> <key>KeepAlive</key> <false/> #{content.gsub(/^/, ' ').strip} </dict> </plist> EOS end |
.plist_path ⇒ Object
Path to the launchctl plist file of the service.
180 181 182 |
# File 'lib/potluck/service.rb', line 180 def self.plist_path File.join(DIR, "#{launchctl_name}.plist") end |
.pretty_name ⇒ Object
Human-friendly name of the service.
159 160 161 |
# File 'lib/potluck/service.rb', line 159 def self.pretty_name @pretty_name ||= self.to_s.split('::').last end |
.service_name ⇒ Object
Computer-friendly name of the service.
166 167 168 |
# File 'lib/potluck/service.rb', line 166 def self.service_name @service_name ||= pretty_name.downcase end |
.write_plist ⇒ Object
Writes the service’s launchctl plist file to disk.
209 210 211 212 |
# File 'lib/potluck/service.rb', line 209 def self.write_plist FileUtils.mkdir_p(File.dirname(plist_path)) File.write(plist_path, plist) end |
Instance Method Details
#log(message, error = false) ⇒ Object
Logs a message using the logger or stdout/stderr if no logger is configured.
-
message
- Message to log. -
error
- True if the message is an error (default: false).
148 149 150 151 152 153 154 |
# File 'lib/potluck/service.rb', line 148 def log(, error = false) if @logger error ? @logger.error() : @logger.info() else error ? $stderr.puts() : $stdout.puts() end end |
#manage? ⇒ Boolean
Returns true if the service is managed.
46 47 48 |
# File 'lib/potluck/service.rb', line 46 def manage? @manage end |
#manage_with_launchctl? ⇒ Boolean
Returns true if the service is managed via launchctl.
53 54 55 |
# File 'lib/potluck/service.rb', line 53 def manage_with_launchctl? @manage_with_launchctl end |
#restart ⇒ Object
Restarts the service if it’s managed by calling stop and then start.
116 117 118 119 120 121 |
# File 'lib/potluck/service.rb', line 116 def restart return unless manage? stop start end |
#run(command, capture_stderr: true) ⇒ Object
Runs a command with the default shell. Raises an error if the command exits with a non-zero status.
-
command
- Command to run. -
capture_stderr
- True if stderr should be redirected to stdout; otherwise stderr output will not be logged (default: true).
130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/potluck/service.rb', line 130 def run(command, capture_stderr: true) output = `#{command}#{' 2>&1' if capture_stderr}` status = $? if status != 0 output.split("\n").each { |line| log(line, :error) } raise(ServiceError, "Command exited with status #{status.exitstatus}: #{command}") else output end end |
#start ⇒ Object
Starts the service if it’s managed and is not active.
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/potluck/service.rb', line 81 def start return unless manage? case status when :error then stop when :active then return end self.class.write_plist if manage_with_launchctl? run(start_command) wait { status == :inactive } raise(ServiceError, "Could not start #{self.class.pretty_name}") if status != :active log("#{self.class.pretty_name} started") end |
#status ⇒ Object
Returns the status of the service:
-
:active
if the service is managed and running. -
:inactive
if the service is not managed or is not running. -
:error
if the service is managed and is in an error state.
64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/potluck/service.rb', line 64 def status return :inactive unless manage? output = `#{status_command}` if $? != 0 :inactive elsif status_error_regex && output[status_error_regex] :error else :active end end |
#stop ⇒ Object
Stops the service if it’s managed and is active or in an error state.
101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/potluck/service.rb', line 101 def stop return unless manage? && status != :inactive self.class.write_plist if manage_with_launchctl? run(stop_command) wait { status != :inactive } raise(ServiceError, "Could not stop #{self.class.pretty_name}") if status != :inactive log("#{self.class.pretty_name} stopped") end |