locd 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|