locd 0.1.3 → 0.1.4

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