locd 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/exe/locd +1 -9
- data/lib/locd/agent.rb +108 -78
- data/lib/locd/agent/job.rb +3 -2
- data/lib/locd/agent/site.rb +1 -1
- data/lib/locd/agent/system.rb +1 -1
- data/lib/locd/cli/command/agent.rb +260 -53
- data/lib/locd/cli/command/job.rb +38 -30
- data/lib/locd/cli/command/main.rb +3 -10
- data/lib/locd/cli/command/proxy.rb +2 -2
- data/lib/locd/cli/command/rotate_logs.rb +24 -7
- data/lib/locd/label.rb +3 -3
- data/lib/locd/launchctl.rb +38 -1
- data/lib/locd/logging.rb +49 -10
- data/lib/locd/newsyslog.rb +1 -4
- data/lib/locd/pattern.rb +41 -5
- data/lib/locd/version.rb +1 -1
- data/locd.gemspec +3 -3
- metadata +8 -8
data/lib/locd/cli/command/job.rb
CHANGED
@@ -54,6 +54,28 @@ class Locd::CLI::Command::Job < Locd::CLI::Command::Agent
|
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
|
+
def get_start_interval
|
58
|
+
if options[:every] && options[:at]
|
59
|
+
raise Thor::ConflictingArgumentError,
|
60
|
+
"Don't supply both `every` and `at` options"
|
61
|
+
end
|
62
|
+
|
63
|
+
start_interval = if options[:every]
|
64
|
+
self.class.parse_every_option options[:every]
|
65
|
+
elsif options[:at]
|
66
|
+
options[:at].map { |key, value|
|
67
|
+
[key.to_sym, value.to_i]
|
68
|
+
}.to_h
|
69
|
+
else
|
70
|
+
raise Thor::RequiredArgumentMissingError,
|
71
|
+
"Must provide `every` or `at` option"
|
72
|
+
end
|
73
|
+
|
74
|
+
logger.debug "Parsed start interval", start_interval: start_interval
|
75
|
+
|
76
|
+
start_interval
|
77
|
+
end
|
78
|
+
|
57
79
|
# end protected
|
58
80
|
public
|
59
81
|
|
@@ -61,7 +83,7 @@ class Locd::CLI::Command::Job < Locd::CLI::Command::Agent
|
|
61
83
|
# Write Commands
|
62
84
|
# ----------------------------------------------------------------------------
|
63
85
|
|
64
|
-
desc "add CMD_TEMPLATE...",
|
86
|
+
desc "add [OPTIONS] -- CMD_TEMPLATE...",
|
65
87
|
"Add job that runs in the current directory"
|
66
88
|
include_options groups: [:write, :add, :respond_with_agents]
|
67
89
|
option :every,
|
@@ -70,41 +92,27 @@ class Locd::CLI::Command::Job < Locd::CLI::Command::Agent
|
|
70
92
|
option :at,
|
71
93
|
desc: "Day/time to start the job",
|
72
94
|
type: :hash
|
73
|
-
def add *cmd_template
|
74
|
-
logger.
|
95
|
+
def add *cmd_template, **kwds
|
96
|
+
logger.trace "#{ self.class.name }##{ __method__ }",
|
75
97
|
options: options,
|
76
98
|
cmd_template: cmd_template,
|
99
|
+
kwds: kwds,
|
77
100
|
shared: self.class.shared_method_options
|
78
101
|
|
79
|
-
|
80
|
-
logger.error "Don't supply both `every` and `at` options",
|
81
|
-
options: options
|
82
|
-
exit 1
|
83
|
-
end
|
84
|
-
|
85
|
-
start_interval = if options[:every]
|
86
|
-
self.class.parse_every_option options[:every]
|
87
|
-
elsif options[:at]
|
88
|
-
options[:at].map { |key, value|
|
89
|
-
[key.to_sym, value.to_i]
|
90
|
-
}.to_h
|
91
|
-
else
|
92
|
-
raise Thor::RequiredArgumentMissingError,
|
93
|
-
"Must provide `every` or `at` option"
|
94
|
-
end
|
95
|
-
|
96
|
-
logger.debug "Parsed start interval", start_interval: start_interval
|
97
|
-
|
98
|
-
job = agent_class.add \
|
99
|
-
cmd_template: cmd_template,
|
100
|
-
start_interval: start_interval,
|
101
|
-
**option_kwds( groups: :write )
|
102
|
-
|
103
|
-
logger.info "`#{ job.label }` job added."
|
102
|
+
kwds[:start_interval] ||= get_start_interval
|
104
103
|
|
105
|
-
|
104
|
+
super *cmd_template, **kwds
|
106
105
|
|
107
|
-
|
106
|
+
# job = agent_class.add \
|
107
|
+
# cmd_template: cmd_template,
|
108
|
+
# start_interval: get_start_interval,
|
109
|
+
# **option_kwds( groups: [:write, :add] )
|
110
|
+
#
|
111
|
+
# logger.info "`#{ job.label }` job added."
|
112
|
+
#
|
113
|
+
# job.reload if options[:load]
|
114
|
+
#
|
115
|
+
# respond job
|
108
116
|
end
|
109
117
|
|
110
118
|
end # class Locd::CLI::Command::Job
|
@@ -40,22 +40,14 @@ class Locd::CLI::Command::Main < Locd::CLI::Command::Base
|
|
40
40
|
LOG_LEVEL_STRINGS = SemanticLogger::LEVELS.map { |sym| sym.to_s.freeze }
|
41
41
|
|
42
42
|
|
43
|
-
# Mixins
|
44
|
-
# ============================================================================
|
45
|
-
|
46
|
-
# Dynamically add {.logger} and {#logger} methods.
|
47
|
-
#
|
48
|
-
# Needs to be in `no_commands` or Thor gets upset.
|
49
|
-
#
|
50
|
-
# no_commands { include SemanticLogger::Loggable }
|
51
|
-
|
52
|
-
|
53
43
|
# Global (Class-Level) Options
|
54
44
|
# ============================================================================
|
55
45
|
#
|
56
46
|
# Applicable to all commands.
|
57
47
|
#
|
58
48
|
|
49
|
+
common_class_options :backtrace
|
50
|
+
|
59
51
|
class_option :log_level,
|
60
52
|
desc: "Set log level",
|
61
53
|
type: :string,
|
@@ -137,6 +129,7 @@ class Locd::CLI::Command::Main < Locd::CLI::Command::Base
|
|
137
129
|
end
|
138
130
|
|
139
131
|
Locd::Logging.level = level unless level.nil?
|
132
|
+
Locd::Logging.set_config_levels
|
140
133
|
|
141
134
|
if [:trace, :debug].include? Locd::Logging.level
|
142
135
|
logger.send Locd::Logging.level, "Hello! We about to start the show..."
|
@@ -69,10 +69,10 @@ class Locd::CLI::Command::Proxy < Locd::CLI::Command::Agent
|
|
69
69
|
# Commands
|
70
70
|
# ============================================================================
|
71
71
|
|
72
|
-
desc "
|
72
|
+
desc "add",
|
73
73
|
"Add agents that runs a command in the current directory"
|
74
74
|
include_options groups: [:write, :add, :respond_with_agents]
|
75
|
-
def
|
75
|
+
def add
|
76
76
|
agent = agent_class.add **option_kwds( groups: :write )
|
77
77
|
|
78
78
|
logger.info "`#{ agent.label }` agent created."
|
@@ -27,7 +27,7 @@ using NRSER::Types
|
|
27
27
|
|
28
28
|
# Manage log rotation.
|
29
29
|
#
|
30
|
-
class Locd::CLI::Command::RotateLogs < Locd::CLI::Command::
|
30
|
+
class Locd::CLI::Command::RotateLogs < Locd::CLI::Command::Job
|
31
31
|
|
32
32
|
|
33
33
|
# Helpers
|
@@ -66,17 +66,34 @@ class Locd::CLI::Command::RotateLogs < Locd::CLI::Command::Agent
|
|
66
66
|
# Commands
|
67
67
|
# ============================================================================
|
68
68
|
|
69
|
-
desc "
|
69
|
+
desc "add [OPTIONS] [-- CMD_TEMPLATE...]",
|
70
70
|
"Add agents that runs a command in the current directory"
|
71
71
|
include_options groups: [:write, :add, :respond_with_agents]
|
72
|
-
def
|
73
|
-
|
72
|
+
def add *cmd_template, **kwds
|
73
|
+
if cmd_template.empty? || cmd_template.all?( &:empty? )
|
74
|
+
cmd_template = agent_class.default_cmd_template
|
75
|
+
end
|
76
|
+
|
77
|
+
if options.key? :label
|
78
|
+
raise Thor::ProhibitedArgumentError,
|
79
|
+
"Label can not be customized on system agents"
|
80
|
+
end
|
74
81
|
|
75
|
-
|
82
|
+
kwds[:start_interval] ||= if options[:every] || options[:at]
|
83
|
+
get_start_interval
|
84
|
+
else
|
85
|
+
agent_class.default_start_interval
|
86
|
+
end
|
76
87
|
|
77
|
-
|
88
|
+
super *cmd_template, **kwds
|
78
89
|
|
79
|
-
|
90
|
+
# agent = agent_class.add **add_kwds
|
91
|
+
#
|
92
|
+
# logger.info "`#{ agent.label }` agent created."
|
93
|
+
#
|
94
|
+
# agent.reload if options[:load]
|
95
|
+
#
|
96
|
+
# respond agent
|
80
97
|
end
|
81
98
|
|
82
99
|
|
data/lib/locd/label.rb
CHANGED
@@ -25,7 +25,7 @@ module Locd::Label
|
|
25
25
|
# @param [String] string
|
26
26
|
# Glob-style pattern to search for in {Locd::Agent#label} strings.
|
27
27
|
#
|
28
|
-
# @param [Boolean]
|
28
|
+
# @param [Boolean] full:
|
29
29
|
# When `false`, the returned Regexp will match any part of label
|
30
30
|
# strings.
|
31
31
|
#
|
@@ -36,7 +36,7 @@ module Locd::Label
|
|
36
36
|
# Makes the Regexp case-insensitive.
|
37
37
|
#
|
38
38
|
#
|
39
|
-
def self.regexp_for_glob string,
|
39
|
+
def self.regexp_for_glob string, full: false, ignore_case: false
|
40
40
|
string.
|
41
41
|
# Uncommon option: `-1` causes empty segments to be included, which
|
42
42
|
# fixes the issue of `*` at start or end
|
@@ -45,7 +45,7 @@ module Locd::Label
|
|
45
45
|
join( '.*' ).
|
46
46
|
thru { |regexp_string|
|
47
47
|
# Add `\A` and `\z` caps if `exact` is set
|
48
|
-
regexp_string = "\\A#{ regexp_string }\\z" if
|
48
|
+
regexp_string = "\\A#{ regexp_string }\\z" if full
|
49
49
|
|
50
50
|
Regexp.new regexp_string, ignore_case
|
51
51
|
}
|
data/lib/locd/launchctl.rb
CHANGED
@@ -31,6 +31,9 @@ using NRSER::Types
|
|
31
31
|
#
|
32
32
|
module Locd::Launchctl
|
33
33
|
|
34
|
+
# Constants
|
35
|
+
# ============================================================================
|
36
|
+
|
34
37
|
# Mappings between any option keyword names we use to the ones `launchctl`
|
35
38
|
# expects.
|
36
39
|
#
|
@@ -53,10 +56,24 @@ module Locd::Launchctl
|
|
53
56
|
STATUS_CACHE_TIMEOUT_SEC = 5
|
54
57
|
|
55
58
|
|
59
|
+
# Mixins
|
60
|
+
# ============================================================================
|
61
|
+
|
56
62
|
# Add {.logger} method
|
57
63
|
include SemanticLogger::Loggable
|
58
64
|
|
59
65
|
|
66
|
+
# Classes
|
67
|
+
# ============================================================================
|
68
|
+
|
69
|
+
class Error < StandardError; end
|
70
|
+
|
71
|
+
|
72
|
+
# Raised when we failed to parse some output from `launchctl`.
|
73
|
+
#
|
74
|
+
class ParseError < Error; end
|
75
|
+
|
76
|
+
|
60
77
|
# `launchctl` executable to use. You shouldn't need to touch this, but might
|
61
78
|
# be useful for testing or if you have path weirdness.
|
62
79
|
#
|
@@ -141,7 +158,27 @@ module Locd::Launchctl
|
|
141
158
|
|
142
159
|
|
143
160
|
def self.disabled
|
144
|
-
|
161
|
+
uid = Cmds.chomp! 'id -u'
|
162
|
+
result = execute :'print-disabled', "user/#{ uid }"
|
163
|
+
|
164
|
+
lines = if match = /disabled\ services\ =\ \{([^\}]+)\}/.match( result.out )
|
165
|
+
match[1].lines
|
166
|
+
else
|
167
|
+
raise ParseError.new binding.erb <<~END
|
168
|
+
Unable to find 'disabled services' section output
|
169
|
+
|
170
|
+
Command:
|
171
|
+
|
172
|
+
<%= result.cmd %>
|
173
|
+
|
174
|
+
Output:
|
175
|
+
|
176
|
+
<%= result.out %>
|
177
|
+
|
178
|
+
END
|
179
|
+
end
|
180
|
+
|
181
|
+
lines
|
145
182
|
end
|
146
183
|
|
147
184
|
|
data/lib/locd/logging.rb
CHANGED
@@ -272,26 +272,65 @@ module Locd::Logging
|
|
272
272
|
end
|
273
273
|
|
274
274
|
|
275
|
-
def self.
|
276
|
-
if
|
275
|
+
def self.get_config_level
|
276
|
+
if Locd.config[:trace].truthy?
|
277
277
|
return :trace
|
278
|
-
elsif
|
278
|
+
elsif Locd.config[:debug].truthy?
|
279
279
|
return :debug
|
280
|
-
elsif
|
281
|
-
return
|
280
|
+
elsif level = Locd.config[:log, :level]
|
281
|
+
return level.to_sym
|
282
282
|
end
|
283
283
|
|
284
284
|
nil
|
285
285
|
end
|
286
286
|
|
287
287
|
|
288
|
+
def self.set_config_levels
|
289
|
+
if levels = Locd.config[:log, :levels]
|
290
|
+
levels.each do |name, level|
|
291
|
+
const = begin
|
292
|
+
NRSER.to_const name
|
293
|
+
rescue
|
294
|
+
next
|
295
|
+
end
|
296
|
+
|
297
|
+
level = level.to_sym
|
298
|
+
|
299
|
+
self.logger.debug "Setting logger level",
|
300
|
+
logger: const.logger,
|
301
|
+
level: level
|
302
|
+
|
303
|
+
const.logger.level = level
|
304
|
+
|
305
|
+
self.logger.debug "Logger level set",
|
306
|
+
logger: const.logger
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
|
288
312
|
# Setup logging.
|
289
313
|
#
|
290
|
-
# @param [
|
291
|
-
#
|
314
|
+
# @param [Symbol?] level:
|
315
|
+
# Optional log level. If provided, it takes precedence. Otherwise, we will
|
316
|
+
# look for log levels in the ENV vars. If nothing is found, no default
|
317
|
+
# log level will be set - it will remain whatever default it already is
|
318
|
+
# (usually `:info` unless something else has changed it).
|
319
|
+
#
|
320
|
+
# @param [Boolean] sync:
|
321
|
+
# When `true`, we will hack out Semantic Logger's async (threaded)
|
322
|
+
# processor and install a synchronous one, which is what you want when
|
323
|
+
# running from the CLI so that output is in order with execution (we don't
|
324
|
+
# care about the performance implications there).
|
325
|
+
#
|
326
|
+
# @param [IO | Hash | String] dest:
|
327
|
+
# Set the appender, see {.appender=}.
|
328
|
+
#
|
329
|
+
# @return [true]
|
330
|
+
# If logging was setup.
|
292
331
|
#
|
293
|
-
# @return [
|
294
|
-
#
|
332
|
+
# @return [false]
|
333
|
+
# If logging has already been setup (warning will be logged as well).
|
295
334
|
#
|
296
335
|
def self.setup level: nil, sync: false, dest: nil
|
297
336
|
if setup?
|
@@ -301,7 +340,7 @@ module Locd::Logging
|
|
301
340
|
|
302
341
|
SemanticLogger.application = 'locd'
|
303
342
|
|
304
|
-
level =
|
343
|
+
level = get_config_level if level.nil?
|
305
344
|
self.level = level if level
|
306
345
|
self.appender = dest if dest
|
307
346
|
|
data/lib/locd/newsyslog.rb
CHANGED
@@ -221,10 +221,7 @@ module Locd::Newsyslog
|
|
221
221
|
workdir = workdir.to_pn
|
222
222
|
|
223
223
|
# Collect the unique log paths
|
224
|
-
log_paths =
|
225
|
-
agent.out_path,
|
226
|
-
agent.err_path,
|
227
|
-
].compact.uniq
|
224
|
+
log_paths = agent.log_paths
|
228
225
|
|
229
226
|
if log_paths.empty?
|
230
227
|
logger.info "Agent #{ agent.label } has no log files."
|
data/lib/locd/pattern.rb
CHANGED
@@ -19,6 +19,28 @@ class Locd::Pattern
|
|
19
19
|
# 1. {String} - a new pattern will be constructed.
|
20
20
|
# 2. {Locd::Pattern} - will just be returned.
|
21
21
|
#
|
22
|
+
# @param [Hash<Symbol, Object>] options
|
23
|
+
# Options that will be passed to the concrete class constructor when
|
24
|
+
# `object` is a string.
|
25
|
+
#
|
26
|
+
# @option options [Boolean] full:
|
27
|
+
# When `true`, pattern must match entire labels.
|
28
|
+
#
|
29
|
+
# When `false`, pattern may match any portion of label.
|
30
|
+
#
|
31
|
+
# Label patterns only.
|
32
|
+
#
|
33
|
+
# @option options [Boolean] ignore_case:
|
34
|
+
# Case-insensitive match (label patterns only).
|
35
|
+
#
|
36
|
+
# @option options [Boolean] recursive:
|
37
|
+
# Additionally match agents with `workdir` in subdirectories (workdir
|
38
|
+
# patterns only).
|
39
|
+
#
|
40
|
+
# @option options [String | Pathname] cwd:
|
41
|
+
# Current working directory to base relative paths from (workdir patterns
|
42
|
+
# only).
|
43
|
+
#
|
22
44
|
# @return [Locd::Pattern::Label | Locd::Pattern::Workdir]
|
23
45
|
# Pattern instance.
|
24
46
|
#
|
@@ -80,7 +102,12 @@ class Locd::Pattern
|
|
80
102
|
end # class Locd::Pattern
|
81
103
|
|
82
104
|
|
83
|
-
# A {Locd::Pattern} that matches against {Locd::Agent} labels
|
105
|
+
# A {Locd::Pattern} that matches against {Locd::Agent} labels with "glob-style"
|
106
|
+
# matching (like the [lunchy](https://rubygems.org/gems/lunchy) gem).
|
107
|
+
#
|
108
|
+
# **_Right now only handles the `*` wildcard!_**
|
109
|
+
#
|
110
|
+
# @see Locd::Label.regexp_for_glob
|
84
111
|
#
|
85
112
|
class Locd::Pattern::Label < Locd::Pattern
|
86
113
|
|
@@ -101,12 +128,21 @@ class Locd::Pattern::Label < Locd::Pattern
|
|
101
128
|
|
102
129
|
# Instantiate a new `Locd::Pattern::Label`.
|
103
130
|
#
|
104
|
-
# @param
|
131
|
+
# @param [String] source
|
132
|
+
# "Glob-style" pattern to search for in {Locd::Agent#label} strings.
|
133
|
+
#
|
134
|
+
# @param [Boolean] full:
|
135
|
+
# When `true`, pattern must match entire label.
|
136
|
+
#
|
137
|
+
# When `false` (default), may match any portion.
|
138
|
+
#
|
139
|
+
# @param [Boolean] ignore_case:
|
140
|
+
# Perform case-insensitive matching.
|
105
141
|
#
|
106
|
-
def initialize source,
|
142
|
+
def initialize source, full: false, ignore_case: false
|
107
143
|
super source
|
108
144
|
@regexp = Locd::Label.regexp_for_glob source,
|
109
|
-
|
145
|
+
full: full,
|
110
146
|
ignore_case: ignore_case
|
111
147
|
end # #initialize
|
112
148
|
|
@@ -163,7 +199,7 @@ class Locd::Pattern::Workdir < Locd::Pattern
|
|
163
199
|
#
|
164
200
|
# See {#recursive}.
|
165
201
|
#
|
166
|
-
# @param [Pathname] cwd:
|
202
|
+
# @param [String | Pathname] cwd:
|
167
203
|
# Directory to expand relative paths against.
|
168
204
|
#
|
169
205
|
def initialize source, recursive: false, cwd: Pathname.getwd
|