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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eb0dc7c5d3b58d3a71a6f37ca805ad663a503d47
4
- data.tar.gz: 0c4c9fe5124b32cdc7774b956bc44b95e80891c5
3
+ metadata.gz: 630061e7d172448491a1da49c70cfac510519d26
4
+ data.tar.gz: 417ac04a01d1bc9e6e888f3e8a1e847407d7b017
5
5
  SHA512:
6
- metadata.gz: 51ec7cb0807e43bebe247e25854e8a2037f524d542b7d55bd6f40433704e3c354c4859335b5c48b72a908dca41fec8f56ccad955bd65a844a6d9e13269ff886b
7
- data.tar.gz: e9e9309a808d16576fc38826a73b6f9034ce870be0b34871f57c360d398d736e61a9508ac2fb46a68f8e6c07925d38e8af334303f8c3e942b6ae4521ba992d7e
6
+ metadata.gz: f3bf11b09b944869c254175fd90984f54959596c9506220d9a79ec49fe6a4ee7d7beb355bff2773b6db552d7bec74f69d3e1c3fb7a313550790ff3334384f72e
7
+ data.tar.gz: 5c03efd66d67973b72a2608ee21c695b2e9aa473c7f9659c713da8a13736799e8941406a2c4d5b2f24ddcfff56915badb785c5ef7c949c580c728d3b49e3d5e5
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.3
1
+ 0.1.4
data/exe/locd CHANGED
@@ -13,12 +13,4 @@ logger = SemanticLogger[__FILE__]
13
13
 
14
14
  require 'locd/cli'
15
15
 
16
- begin
17
- Locd::CLI::Command::Main.start
18
- rescue Exception => e
19
- if e.instance_of?(SystemExit)
20
- raise
21
- else
22
- logger.fatal e
23
- end
24
- end
16
+ Locd::CLI::Command::Main.exec!
@@ -40,14 +40,6 @@ class Locd::Agent
40
40
  # Constants
41
41
  # ==========================================================================
42
42
 
43
- # Extra plist key we store Loc'd config under (this is where the port is
44
- # stored). We also use it's presence to detect that a plist is Loc'd.
45
- #
46
- # @return [String]
47
- #
48
- CONFIG_KEY = 'locd_config'
49
-
50
-
51
43
  # Attribute / method names that {#to_h} uses.
52
44
  #
53
45
  # @return [Hamster::SortedSet<Symbol>]
@@ -69,7 +61,7 @@ class Locd::Agent
69
61
  # ==========================================================================
70
62
 
71
63
  # Test if the parse of a property list is for a Loc'd agent by seeing if
72
- # it has {CONFIG_KEY} as a key.
64
+ # it has the config key (from `Locd.config[:agent, :config_key]`) as a key.
73
65
  #
74
66
  # @param [Hash<String, Object>] plist
75
67
  # {include:file:doc/include/plist.md}
@@ -78,7 +70,7 @@ class Locd::Agent
78
70
  # `true` if the plist looks like it's from Loc'd.
79
71
  #
80
72
  def self.plist? plist
81
- plist.key? CONFIG_KEY
73
+ plist.key? Locd.config[:agent, :config_key]
82
74
  end # .plist?
83
75
 
84
76
 
@@ -351,7 +343,7 @@ class Locd::Agent
351
343
  # Parameters are passed to {Locd::Label.regexp_for_glob} and the resulting
352
344
  # {Regexp} is matched against each agent's {#label}.
353
345
  #
354
- # @see Locd::Label.regexp_for_glob
346
+ # @see Locd::Pattern
355
347
  #
356
348
  # @param (see .find_all)
357
349
  #
@@ -364,7 +356,7 @@ class Locd::Agent
364
356
 
365
357
  # Find all the agents that match a pattern.
366
358
  #
367
- # @see Locd::Label.regexp_for_glob
359
+ # @see Locd::Pattern
368
360
  #
369
361
  # @param [String | Pattern] pattern
370
362
  # Pattern to match against agent.
@@ -539,7 +531,7 @@ class Locd::Agent
539
531
 
540
532
  # Extras we need... `launchd` complains in the system log about this
541
533
  # but it's the easiest way to handle it at the moment
542
- CONFIG_KEY => {
534
+ Locd.config[:agent, :config_key] => {
543
535
  # Save this too why the hell not, might help debuging at least
544
536
  cmd_template: cmd_template,
545
537
 
@@ -636,27 +628,11 @@ class Locd::Agent
636
628
  logger.debug "Property list written", path: plist_abs_path
637
629
 
638
630
  from_path( plist_abs_path ).tap { |agent|
639
- logger.debug { ["{Agent} added", agent: agent] }
631
+ agent.send :log_info, "added"
640
632
  }
641
633
  end # .add
642
634
 
643
635
 
644
- # @!group Removing Agents
645
- # ----------------------------------------------------------------------------
646
-
647
- # Find an agent using a label pattern and call {#remove} on it.
648
- #
649
- # @param (see .find_only!)
650
- # @return (see #remove)
651
- # @raise See {.find_only!} and {#remove}
652
- #
653
- def self.remove *find_only_args
654
- Locd::Agent.find_only!( *find_only_args ).remove
655
- end # .remove
656
-
657
- # @!endgroup
658
-
659
-
660
636
  # Attributes
661
637
  # ============================================================================
662
638
 
@@ -689,9 +665,9 @@ class Locd::Agent
689
665
 
690
666
  # Sanity check...
691
667
 
692
- unless plist.key? CONFIG_KEY
668
+ unless plist.key? Locd.config[:agent, :config_key]
693
669
  raise ArgumentError.new binding.erb <<~END
694
- Not a Loc'd plist (no <%= Locd::Agent::CONFIG_KEY %> key)
670
+ Not a Loc'd plist (no <%= Locd.config[:agent, :config_key] %> key)
695
671
 
696
672
  path: <%= path %>
697
673
  plist:
@@ -779,7 +755,7 @@ class Locd::Agent
779
755
 
780
756
 
781
757
  def config
782
- plist[CONFIG_KEY]
758
+ plist[Locd.config[:agent, :config_key]]
783
759
  end
784
760
 
785
761
 
@@ -822,6 +798,18 @@ class Locd::Agent
822
798
  plist['StandardErrorPath'].to_pn if plist['StandardErrorPath']
823
799
  end
824
800
 
801
+
802
+ # Get a list of all unique log paths.
803
+ #
804
+ # @return [Array<Pathname>]
805
+ #
806
+ def log_paths
807
+ [
808
+ out_path,
809
+ err_path,
810
+ ].compact.uniq
811
+ end
812
+
825
813
  # @!endgroup Instance Methods: Attribute Readers
826
814
 
827
815
 
@@ -875,15 +863,15 @@ class Locd::Agent
875
863
  # @param [Boolean] force:
876
864
  # Force the loading of the plist. Ignore the `launchd` *Disabled* key.
877
865
  #
878
- # @param [Boolean] write:
866
+ # @param [Boolean] enable:
879
867
  # Overrides the `launchd` *Disabled* key and sets it to `false`.
880
868
  #
881
869
  # @return [self]
882
870
  #
883
- def load force: false, write: false
884
- logger.debug "Loading #{ label } agent...", force: force, write: write
871
+ def load force: false, enable: false
872
+ logger.debug "Loading #{ label } agent...", force: force, enable: enable
885
873
 
886
- result = Locd::Launchctl.load! path, force: force, write: write
874
+ result = Locd::Launchctl.load! path, force: force, write: enable
887
875
 
888
876
  message = if result.err =~ /service\ already\ loaded/
889
877
  "already loaded"
@@ -891,7 +879,7 @@ class Locd::Agent
891
879
  "LOADED"
892
880
  end
893
881
 
894
- status_info message, status: status
882
+ log_info message, status: status
895
883
 
896
884
  self
897
885
  end
@@ -901,17 +889,17 @@ class Locd::Agent
901
889
  #
902
890
  # This is a bit low-level; you probably want to use {#stop}.
903
891
  #
904
- # @param [Boolean] write:
892
+ # @param [Boolean] disable:
905
893
  # Overrides the `launchd` *Disabled* key and sets it to `true`.
906
894
  #
907
895
  # @return [self]
908
896
  #
909
- def unload write: false
910
- logger.debug "Unloading #{ label } agent...", write: write
897
+ def unload disable: false
898
+ logger.debug "Unloading #{ label } agent...", disable: disable
911
899
 
912
- result = Locd::Launchctl.unload! path, write: write
900
+ result = Locd::Launchctl.unload! path, write: disable
913
901
 
914
- status_info "UNLOADED"
902
+ log_info "UNLOADED"
915
903
 
916
904
  self
917
905
  end
@@ -922,9 +910,9 @@ class Locd::Agent
922
910
  # @param (see #load)
923
911
  # @return (see #load)
924
912
  #
925
- def reload force: false, write: false
913
+ def reload force: false, enable: false
926
914
  unload
927
- load force: force, write: write
915
+ load force: force, enable: enable
928
916
  end
929
917
 
930
918
 
@@ -935,23 +923,23 @@ class Locd::Agent
935
923
  # start, even if it's {#disabled?}).
936
924
  #
937
925
  # @param [Boolean] load:
938
- # Call {#load} first, passing it `write` and `force`.
926
+ # Call {#load} first, passing it `enable` and `force`.
939
927
  #
940
- # @param force (see #load)
941
- # @param write (see #load)
928
+ # @param force (see #load)
929
+ # @param enable (see #load)
942
930
  #
943
931
  # @return [self]
944
932
  #
945
- def start load: true, force: true, write: false
933
+ def start load: true, force: false, enable: false
946
934
  logger.trace "Starting `#{ label }` agent...",
947
935
  load: load,
948
936
  force: force,
949
- write: write
937
+ enable: enable
950
938
 
951
- self.load( force: force, write: write ) if load
939
+ self.load( force: force, enable: enable ) if load
952
940
 
953
941
  Locd::Launchctl.start! label
954
- status_info "STARTED"
942
+ log_info "STARTED"
955
943
 
956
944
  self
957
945
  end
@@ -962,19 +950,19 @@ class Locd::Agent
962
950
  # @param [Boolean] unload:
963
951
  # Call {#unload} first, passing it `write`.
964
952
  #
965
- # @param write (see #unload)
953
+ # @param disable (see #unload)
966
954
  #
967
955
  # @return [self]
968
956
  #
969
- def stop unload: true, write: false
957
+ def stop unload: true, disable: false
970
958
  logger.debug "Stopping `#{ label } agent...`",
971
959
  unload: unload,
972
- write: write
960
+ disable: disable
973
961
 
974
962
  Locd::Launchctl.stop label
975
- status_info "STOPPED"
963
+ log_info "STOPPED"
976
964
 
977
- self.unload( write: write ) if unload
965
+ self.unload( disable: disable ) if unload
978
966
 
979
967
  self
980
968
  end
@@ -982,28 +970,44 @@ class Locd::Agent
982
970
 
983
971
  # Restart the agent ({#stop} then {#start}).
984
972
  #
985
- # @param unload (see #stop)
973
+ # @param [Boolean] reload:
974
+ # Unload then load the agent in `launchd`.
975
+ #
986
976
  # @param force (see #start)
987
- # @param write (see #start)
977
+ # @param enable (see #start)
988
978
  #
989
979
  # @return [self]
990
980
  #
991
- def restart unload: true, force: true, write: false
992
- stop unload: unload
993
- start load: unload, force: force, write: write
981
+ def restart reload: true, force: true, enable: false
982
+ stop unload: reload
983
+ start load: reload, force: force, enable: enable
994
984
  end # #restart
995
985
 
996
986
 
997
987
  # Remove the agent by removing it's {#path} file. Will {#stop} and
998
988
  # {#unloads} the agent first.
999
989
  #
990
+ # @param [Boolean] logs:
991
+ # When `true` remove all logs as well.
992
+ #
1000
993
  # @return [self]
1001
994
  #
1002
- def remove
995
+ def remove logs: false
1003
996
  stop unload: true
1004
997
 
998
+ if logs
999
+ log_paths.each { |log_path|
1000
+ if log_path.exists?
1001
+ FileUtils.rm log_path
1002
+ log_info "Removed log", path: log_path.to_s
1003
+ else
1004
+ log_info "Log path does not exist", path: log_path.to_s
1005
+ end
1006
+ }
1007
+ end
1008
+
1005
1009
  FileUtils.rm path
1006
- status_info "REMOVED"
1010
+ log_info "REMOVED"
1007
1011
 
1008
1012
  self
1009
1013
  end
@@ -1011,18 +1015,25 @@ class Locd::Agent
1011
1015
  # @!endgroup Instance Methods: `launchctl` Interface
1012
1016
 
1013
1017
 
1018
+ # @!group Modifying Agents
1019
+ # --------------------------------------------------------------------------
1020
+
1014
1021
  # Update specific values on the agent, which *may* change it's file path if
1015
1022
  # a different label is provided.
1016
1023
  #
1017
1024
  # **_Does not mutate this instance! Returns a new {Locd::Agent} with the
1018
1025
  # updated values._**
1019
1026
  #
1020
- # @param (see .create_plist_data)
1027
+ # @param [Boolean] force:
1028
+ # Overwrite any existing agent with the same label.
1029
+ #
1030
+ # @param [Hash<Symbol, Object>] **values
1031
+ # See {.create_plist_data}.
1021
1032
  #
1022
1033
  # @return [Locd::Agent]
1023
1034
  # A new instance with the updated values.
1024
1035
  #
1025
- def update **values
1036
+ def update force: false, **values
1026
1037
  logger.trace "Updating `#{ label }` agent", **values
1027
1038
 
1028
1039
  # Remove the `cmd_template` if it's nil of an empty array so that
@@ -1066,16 +1077,22 @@ class Locd::Agent
1066
1077
  # another agent
1067
1078
 
1068
1079
  if File.exists? new_plist_abs_path
1069
- # There's someone already there! Bail out
1070
- raise binding.erb <<-END
1071
- A different agent already exists at:
1072
-
1073
- <%= new_plist_abs_path %>
1074
-
1075
- Remove that agent first if you really want to replace it with an
1076
- updated version of this one.
1077
-
1078
- END
1080
+ # There's someone already there!
1081
+ # Bail out unless we are forcing the operation
1082
+ if force
1083
+ logger.info "Overwriting agent #{ new_label } with update to " \
1084
+ "agent #{ label } (force: `true`)"
1085
+ else
1086
+ raise binding.erb <<-END
1087
+ A different agent already exists at:
1088
+
1089
+ <%= new_plist_abs_path %>
1090
+
1091
+ Remove that agent first if you really want to replace it with an
1092
+ updated version of this one or provide `force: true`.
1093
+
1094
+ END
1095
+ end
1079
1096
  end
1080
1097
 
1081
1098
  # Ok, we're in the clear (save for the obvious race condition, but,
@@ -1092,6 +1109,7 @@ class Locd::Agent
1092
1109
  end
1093
1110
  end # #update
1094
1111
 
1112
+ # @!endgroup Modifying Agents
1095
1113
 
1096
1114
 
1097
1115
  # @!group Instance Methods: Language Integration
@@ -1167,8 +1185,20 @@ class Locd::Agent
1167
1185
  end
1168
1186
 
1169
1187
 
1170
- def status_info message, **details
1171
- logger.info "Agent `#{ label }` #{ message }", **details
1188
+ # Log a message with a details payload, prefixing the agent's label.
1189
+ #
1190
+ # Used to relay info to the user in the CLI.
1191
+ #
1192
+ # @param [#to_s] message
1193
+ # Message to log.
1194
+ #
1195
+ # @param [Hash<Symbol, Object>] **payload
1196
+ # Optional values to dump.
1197
+ #
1198
+ # @return [void]
1199
+ #
1200
+ def log_info message, **payload
1201
+ logger.info "Agent `#{ label }` #{ message }", **payload
1172
1202
  end
1173
1203
 
1174
1204
  # end protected
@@ -113,11 +113,12 @@ class Locd::Agent::Job < Locd::Agent
113
113
 
114
114
  start_interval_data = t.match start_interval,
115
115
  t.pos_int,
116
- {"StartInterval" => start_interval},
116
+ { "StartInterval" => start_interval },
117
117
 
118
118
  Types.start_calendar_interval,
119
119
  ->( interval ) {
120
- interval.map { |key, value| [key.to_s.capitalize, value] }.to_h
120
+ { 'StartCalendarInterval' =>
121
+ interval.map { |key, value| [key.to_s.capitalize, value] }.to_h }
121
122
  },
122
123
 
123
124
  Types.start_calendar_intervals,
@@ -76,7 +76,7 @@ class Locd::Agent::Site < Locd::Agent
76
76
  # `true` if we think this `plist` belongs to a site.
77
77
  #
78
78
  def self.plist? plist
79
- !! plist.dig( Locd::Agent::CONFIG_KEY, 'port' )
79
+ !! plist.dig( Locd.config[:agent, :config_key], 'port' )
80
80
  end # .plist?
81
81
 
82
82
 
@@ -55,7 +55,7 @@ module Locd::Agent::System
55
55
  #
56
56
  def self.plist? plist
57
57
  !!(
58
- plist.dig( Locd::Agent::CONFIG_KEY, 'is_system' ) \
58
+ plist.dig( Locd.config[:agent, :config_key], 'is_system' ) \
59
59
  && label?( plist['Label'] )
60
60
  )
61
61
  end # .plist?
@@ -117,18 +117,36 @@ class Locd::CLI::Command::Agent < Locd::CLI::Command::Base
117
117
  # Options when provided a `PATTERN` argument used to find agents by label.
118
118
  #
119
119
 
120
- shared_option :exact,
120
+ shared_option :full,
121
121
  groups: :pattern,
122
- desc: "Force PATTERN to match entire label string",
123
- aliases: '-x',
122
+ desc: "Require label PATTERN to match entire string",
123
+ aliases: '-u',
124
+ type: :boolean
125
+
126
+ shared_option :ignore_case,
127
+ groups: :pattern,
128
+ desc: "Make label PATTERN case-insensitive",
129
+ aliases: '-i',
124
130
  type: :boolean
125
131
 
126
132
  shared_option :recursive,
127
133
  groups: :pattern,
128
- desc: "Workdir patterns match all subdirs too",
134
+ desc: "Make workdir PATTERN match all subdirs too",
129
135
  aliases: '-r',
130
136
  type: :boolean
131
137
 
138
+ # NOTE We don't expose the workdir pattern's `:cwd` option...
139
+ # If you want to match a directory from the CLI, just provide that
140
+ # directory... no reason to specify the `:cwd` in an option then
141
+ # provide a relative PATTERN
142
+ #
143
+
144
+ # `:multi` Group
145
+ # ----------------------------------------------------------------------------
146
+ #
147
+ # For commands that can match multiple agents.
148
+ #
149
+
132
150
  shared_option :all,
133
151
  groups: :multi,
134
152
  desc: "Apply to ALL agents that PATTERN matches",
@@ -161,11 +179,25 @@ class Locd::CLI::Command::Agent < Locd::CLI::Command::Base
161
179
  aliases: ['--log'],
162
180
  type: :string
163
181
 
182
+ shared_option :keep_alive,
183
+ groups: :write,
184
+ desc: "Try to keep the agent running",
185
+ type: :boolean,
186
+ default: false
187
+
188
+ shared_option :run_at_load,
189
+ groups: :write,
190
+ desc: "Start the agent when loading it",
191
+ type: :boolean,
192
+ default: false
193
+
194
+
195
+ # `:add` Group
196
+ # ----------------------------------------------------------------------------
164
197
 
165
198
  shared_option :force,
166
199
  groups: :add,
167
200
  desc: "Overwrite any existing agent",
168
- aliases: '-f',
169
201
  type: :boolean,
170
202
  default: false
171
203
 
@@ -177,12 +209,6 @@ class Locd::CLI::Command::Agent < Locd::CLI::Command::Base
177
209
  default: true
178
210
 
179
211
 
180
- shared_option :unload,
181
- groups: :start,
182
- desc: "Unload the agent from `launchd` after stopping",
183
- type: :boolean,
184
- default: true
185
-
186
212
  # Commands
187
213
  # ============================================================================
188
214
 
@@ -191,9 +217,12 @@ class Locd::CLI::Command::Agent < Locd::CLI::Command::Base
191
217
 
192
218
  desc "ls [PATTERN]",
193
219
  "List agents"
220
+
194
221
  map list: :ls
222
+
195
223
  include_options :long,
196
224
  groups: :pattern
225
+
197
226
  def ls pattern = nil
198
227
  results = if pattern.nil?
199
228
  agent_class.all
@@ -205,20 +234,74 @@ class Locd::CLI::Command::Agent < Locd::CLI::Command::Base
205
234
  end
206
235
 
207
236
 
208
- # Write Commands
237
+ desc "plist PATTERN",
238
+ "Print an agent's launchd property list"
239
+
240
+ include_options groups: :pattern
241
+
242
+ def plist pattern
243
+ agent = find_only! pattern
244
+
245
+ if options[:json] || options[:yaml]
246
+ respond agent.plist
247
+ else
248
+ respond agent.path.read
249
+ end
250
+ end
251
+
252
+
253
+ desc 'status PATTERN',
254
+ "Print agent status"
255
+
256
+ include_options groups: :pattern
257
+
258
+ def status pattern
259
+ agent = find_only! pattern
260
+ respond \
261
+ label: agent.label,
262
+ port: agent.port,
263
+ status: agent.status
264
+ end
265
+
266
+
267
+ # Commands for Manipulating Agent Definitions
209
268
  # ----------------------------------------------------------------------------
210
269
 
211
270
  desc "add CMD_TEMPLATE...",
212
- "Add agents that runs a command in the current directory"
271
+ "Add an agent that runs a command in the current directory"
272
+
213
273
  include_options groups: [:write, :add, :respond_with_agents]
214
- def add *cmd_template
215
- agent = agent_class.add \
274
+
275
+ def add *cmd_template, **kwds
276
+ logger.trace __method__.to_s,
216
277
  cmd_template: cmd_template,
217
- **option_kwds( groups: :write )
278
+ kwds: kwds,
279
+ options: options
218
280
 
219
- logger.info "`#{ agent.label }` agent created."
281
+ # Merge all the keywords together into the format needed to call
282
+ # {Locd::Agent.add}
283
+ kwds.merge! **option_kwds( :force, groups: [:write] ),
284
+ cmd_template: cmd_template
220
285
 
221
- agent.load if options[:load]
286
+ # Check args
287
+
288
+ # `:cmd_template` can not be empty at this point
289
+ if kwds[:cmd_template].empty? || kwds[:cmd_template].all?( &:empty? )
290
+ raise Thor::RequiredArgumentMissingError,
291
+ "CMD_TEMPLATE argument is required to add an agent"
292
+ end
293
+
294
+ # Need a `:label` too
295
+ unless t.non_empty_str === kwds[:label]
296
+ raise Thor::RequiredArgumentMissingError,
297
+ "--label=LABEL option is required to add an agent"
298
+ end
299
+
300
+ # Do the add
301
+ agent = agent_class.add **kwds
302
+
303
+ # Reload (unless we were told not to... usually you want to reload)
304
+ agent.reload if options[:load]
222
305
 
223
306
  respond agent
224
307
  end
@@ -226,7 +309,9 @@ class Locd::CLI::Command::Agent < Locd::CLI::Command::Base
226
309
 
227
310
  desc "update PATTERN [OPTIONS] [-- CMD_TEMPLATE...]",
228
311
  "Update an existing agent"
312
+
229
313
  include_options groups: [:pattern, :write, :respond_with_agents]
314
+
230
315
  def update pattern, *cmd_template
231
316
  agent = find_only! pattern
232
317
 
@@ -240,71 +325,193 @@ class Locd::CLI::Command::Agent < Locd::CLI::Command::Base
240
325
  end
241
326
 
242
327
 
243
- desc "open PATTERN",
244
- "Open an agent's URL in the browser"
245
- include_options groups: [:pattern, :multi]
246
- def open pattern
247
- find_multi!( pattern ).each do |agent|
248
- Cmds! "open %s", agent.url
249
- logger.info "Opened agent `#{ agent.label }` at #{ agent.url }"
250
- end
251
- end
252
-
253
-
254
328
  desc "rm PATTERN",
255
329
  "Remove (uninstall, delete) a agent"
330
+
256
331
  map remove: :rm
332
+
257
333
  include_options groups: [:pattern, :multi]
334
+
335
+ option :logs,
336
+ desc: "Remove logs too",
337
+ type: :boolean,
338
+ default: false
339
+
258
340
  def rm pattern
259
- agent_class.remove pattern, **option_kwds( :exact )
341
+ kwds = option_kwds :logs
342
+ find_multi!( pattern ).each { |agent| agent.remove **kwds }
260
343
  end
261
344
 
262
345
 
263
- desc "plist PATTERN",
264
- "Show an agent's launchd property list"
265
- include_options groups: :pattern
266
- def plist pattern
267
- # TODO Why doesn't this use {#find_only!}?
268
- agent = agent_class.find_only! pattern, **option_kwds( :exact )
269
-
270
- if options[:json] || options[:yaml]
271
- respond agent.plist
272
- else
273
- respond agent.path.read
274
- end
275
- end
346
+ # Commands for Manipulating Agent State
347
+ # --------------------------------------------------------------------------
276
348
 
277
-
278
349
  desc "start PATTERN",
279
350
  "Start an agent"
351
+
280
352
  include_options groups: :pattern
281
- option :write,
353
+
354
+ option :load,
355
+ desc: "Load the agent before starting",
356
+ type: :boolean,
357
+ default: true
358
+
359
+ option :force,
360
+ desc: "Force loading of agent even if it's disabled",
361
+ type: :boolean,
362
+ default: false
363
+
364
+ option :enable,
282
365
  desc: "Set `launchd` *Disabled* key to `false`",
283
- type: :boolean
366
+ type: :boolean,
367
+ default: false
368
+
284
369
  def start pattern
285
- find_only!( pattern ).start
370
+ find_only!( pattern ).start **option_kwds( :load, :force, :enable )
286
371
  end
287
372
 
288
373
 
289
374
  desc "stop PATTERN",
290
375
  "Stop an agent"
376
+
291
377
  include_options groups: [:pattern, :stop]
292
- option :write,
378
+
379
+ option :unload,
380
+ desc: "Unload the agent from `launchd` after stopping",
381
+ type: :boolean,
382
+ default: true
383
+
384
+ option :disable,
293
385
  desc: "Set `launchd` *Disabled* key to `true`",
294
- type: :boolean
386
+ type: :boolean,
387
+ default: false
388
+
295
389
  def stop pattern
296
- find_only!( pattern ).stop **option_kwds( :unload )
390
+ find_only!( pattern ).stop **option_kwds( :unload, :disable )
297
391
  end
298
392
 
299
393
 
300
394
  desc "restart PATTERN",
301
395
  "Restart an agent"
396
+
302
397
  include_options groups: [:pattern, :stop]
303
- option :write,
398
+
399
+ option :reload,
400
+ desc: "Unload and reload the agent in `launchd`",
401
+ type: :boolean,
402
+ default: true
403
+
404
+ option :force,
405
+ desc: "Force loading of agent even if it's disabled",
406
+ type: :boolean,
407
+ default: false
408
+
409
+ option :enable,
304
410
  desc: "Set `launchd` *Disabled* key to `false`",
305
- type: :boolean
411
+ type: :boolean,
412
+ default: false
413
+
306
414
  def restart pattern
307
- find_only!( pattern ).restart
415
+ find_only!( pattern ).restart **option_kwds( :reload, :force, :enable )
308
416
  end
309
417
 
418
+
419
+ # Assorted Other Commands
420
+ # ----------------------------------------------------------------------------
421
+
422
+ desc "open PATTERN",
423
+ "Open an agent's URL in the browser"
424
+
425
+ include_options groups: [:pattern, :multi]
426
+
427
+ def open pattern
428
+ find_multi!( pattern ).each do |agent|
429
+ Cmds! "open %s", agent.url
430
+ logger.info "Opened agent `#{ agent.label }` at #{ agent.url }"
431
+ end
432
+ end
433
+
434
+
435
+ desc 'truncate_logs PATTERN',
436
+ "Truncate agent log file(s)."
437
+
438
+ include_options groups: [:pattern, :multi]
439
+
440
+ option :restart,
441
+ desc: "Restart the agent after truncation",
442
+ type: :boolean,
443
+ default: true
444
+
445
+ def truncate_logs pattern
446
+ find_multi!( pattern ).each do |agent|
447
+ log_paths = [agent.out_path, agent.err_path].compact.uniq
448
+
449
+ unless log_paths.empty?
450
+ restart = options[:restart] && agent.running?
451
+
452
+ agent.stop if restart
453
+
454
+ log_paths.each do |log_path|
455
+ begin
456
+ log_path.open( 'w' ) { |f| f.truncate 0 }
457
+ rescue Exception => error
458
+ logger.error "Failed to truncate #{ log_path }", error
459
+ else
460
+ logger.info "Truncated",
461
+ 'file' => log_path.to_s,
462
+ 'agent.label' => agent.label
463
+ end
464
+ end # each log_path
465
+
466
+ agent.start if restart
467
+ end # unless log_paths.empty?
468
+ end # each agent
469
+ end # #truncate_logs
470
+
471
+
472
+ desc 'tail [OPTIONS] PATTERN [-- TAIL_OPTIONS]',
473
+ "Tail agent logs"
474
+
475
+ include_options groups: :pattern
476
+
477
+ option :stream,
478
+ desc: "Stream to tail. May omit if uses single log file.",
479
+ aliases: ['-s'],
480
+ type: :string,
481
+ enum: ['out', 'err']
482
+
483
+ def tail pattern, *tail_options
484
+ agent = find_only! pattern
485
+
486
+ path = case options[:stream]
487
+ when nil
488
+ paths = agent.log_paths
489
+
490
+ unless paths.length == 1
491
+ raise Thor::RequiredArgumentMissingError.new binding.erb <<~END
492
+ Agent `<%= agent.label %>` has multiple log files.
493
+
494
+ out: <%= agent.out_path.to_s %>
495
+ err: <%= agent.err_path.to_s %>
496
+
497
+ Must specify one via the `--stream` option.
498
+
499
+ END
500
+ end
501
+
502
+ paths[0]
503
+
504
+ when 'out'
505
+ agent.out_path
506
+
507
+ when 'err'
508
+ agent.err_path
509
+
510
+ else
511
+ raise "WTF"
512
+ end
513
+
514
+ exec "tail", *tail_options, path.to_s
515
+ end # #tail
516
+
310
517
  end # class Locd::CLI::Command::Agent