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.
@@ -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.debug "#{ self.class.name }##{ __method__ }",
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
- if options[:every] && options[:at]
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
- job.load if options[:load]
104
+ super *cmd_template, **kwds
106
105
 
107
- respond job
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 "setup",
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 setup
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::Agent
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 "setup",
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 setup
73
- agent = agent_class.add **option_kwds( groups: :write )
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
- logger.info "`#{ agent.label }` agent created."
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
- agent.load if options[:load]
88
+ super *cmd_template, **kwds
78
89
 
79
- respond agent
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
 
@@ -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] exact:
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, exact: false, ignore_case: false
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 exact
48
+ regexp_string = "\\A#{ regexp_string }\\z" if full
49
49
 
50
50
  Regexp.new regexp_string, ignore_case
51
51
  }
@@ -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
- execute :'print-disabled', Cmds.expr( "user/$(id -u)" )
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
 
@@ -272,26 +272,65 @@ module Locd::Logging
272
272
  end
273
273
 
274
274
 
275
- def self.get_env_level
276
- if ENV['LOCD_TRACE'].truthy?
275
+ def self.get_config_level
276
+ if Locd.config[:trace].truthy?
277
277
  return :trace
278
- elsif ENV['LOCD_DEBUG'].truthy?
278
+ elsif Locd.config[:debug].truthy?
279
279
  return :debug
280
- elsif ENV['LOCD_LOG_LEVEL']
281
- return ENV['LOCD_LOG_LEVEL'].to_sym
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 [type] arg_name
291
- # @todo Add name param description.
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 [return_type]
294
- # @todo Document return value.
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 = get_env_level if level.nil?
343
+ level = get_config_level if level.nil?
305
344
  self.level = level if level
306
345
  self.appender = dest if dest
307
346
 
@@ -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."
@@ -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 (see Locd::Label.regexp_for_glob)
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, exact: false, ignore_case: false
142
+ def initialize source, full: false, ignore_case: false
107
143
  super source
108
144
  @regexp = Locd::Label.regexp_for_glob source,
109
- exact: exact,
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