envoi-mam-agent 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,322 @@
1
+ require 'json'
2
+
3
+ require 'envoi/aspera/watch_service/client'
4
+
5
+ module Envoi
6
+ module Aspera
7
+ module WatchService
8
+
9
+ class WatchFolder
10
+ class State
11
+
12
+ attr_accessor :subscription
13
+ attr_accessor :known_path_map
14
+ attr_accessor :details
15
+
16
+
17
+ def initialize
18
+ @known_path_map = {}
19
+ @details = {}
20
+ end
21
+
22
+ end
23
+
24
+ class Subscription
25
+
26
+ class Snapshot
27
+
28
+ class Entry < Hash
29
+
30
+ def initialize(data)
31
+ merge! data
32
+ end
33
+
34
+ def path;
35
+ self[:path]
36
+ end
37
+
38
+ def stat;
39
+ self[:stat] ||= ((json = self[:stat_as_json]) ? JSON.parse(json) : nil)
40
+ end
41
+
42
+ def stat_as_json;
43
+ self[:stat_as_json]
44
+ end
45
+
46
+ if MatchData.method_defined?(:named_captures)
47
+ def self.new_from_match_data(match_data)
48
+ new(match_data.named_captures)
49
+ end
50
+ else
51
+ def self.new_from_match_data(match_data)
52
+ new(Hash[match_data.names.map(&:to_sym).zip(match_data.captures)])
53
+ end
54
+ end
55
+
56
+ # Entry
57
+ end
58
+
59
+ attr_accessor :subscription, :version, :entries
60
+
61
+ def initialize(subscription, version)
62
+ @subscription = subscription
63
+ @version = version
64
+ @entries = nil
65
+ end
66
+
67
+ def client
68
+ subscription.client
69
+ end
70
+
71
+ def logger
72
+ client.logger
73
+ end
74
+
75
+ def entries
76
+ @entries ||= client.subscription_snapshot_entries_get(subscription_id: subscription['identifier'],
77
+ snapshot_version: version)
78
+ end
79
+
80
+ def differential(from = nil)
81
+ (@differentials ||= {})[from] ||= differential_no_cache(from: from)
82
+ end
83
+
84
+ def differential_no_cache(args = {})
85
+ args_out = args.dup
86
+ args_out[:subscription_id] ||= subscription['identifier']
87
+ args_out[:to] ||= version
88
+ client.subscription_snapshot_differential(args_out)
89
+ end
90
+
91
+ def entries_by_path
92
+ @entries_by_hash ||= Hash[entries.map { |e| [e[:path], e] }]
93
+ end
94
+
95
+ # Snapshot
96
+ end
97
+
98
+ attr_accessor :attributes, :client, :definition, :snapshots
99
+
100
+ def initialize(data, client, definition = nil, snapshots = { })
101
+ @attributes = data
102
+ @client = client
103
+ @definition = definition
104
+ @snapshots = {}
105
+ end
106
+
107
+ def logger
108
+ client.logger
109
+ end
110
+
111
+ def [](key)
112
+ attributes[key]
113
+ end
114
+
115
+ def identifier
116
+ attributes['identifier']
117
+ end
118
+
119
+ alias id identifier
120
+
121
+ def snapshot_create(args = {})
122
+ args_out = args.dup
123
+ args_out[:subscription_id] = attributes['identifier']
124
+ snapshot_id = client.subscription_snapshot_create(args_out)
125
+ snapshots[snapshot_id] ||= Snapshot.new(self, snapshot_id)
126
+ end
127
+
128
+ def resubscribe
129
+ client.subscription_resubscribe(subscription_id: id)
130
+ end
131
+
132
+
133
+ def self.get_or_create(client, definition, snapshots = { })
134
+ watch_folder_path = definition['path']
135
+ scan_period = definition['scan_period']
136
+ expire_in = definition['expire_in']
137
+
138
+ subscriptions = nil
139
+ subscriptions_for_path = client.subscription_find_for_path(path: watch_folder_path, subscriptions: subscriptions)
140
+ subscriptions_for_path.delete_if { |s| s['scan_period']['sec'] != scan_period } if scan_period
141
+
142
+ if subscriptions_for_path.empty?
143
+ args_out = { watch_folder_path: watch_folder_path }
144
+ args_out[:scan_period] = scan_period if scan_period
145
+ args_out[:expire_in] = expire_in if expire_in
146
+ # Create Subscription
147
+ subscription = client.subscription_create(args_out)
148
+ else
149
+ subscription = subscriptions_for_path.first
150
+ end
151
+
152
+ new(subscription, client, definition, snapshots)
153
+ end
154
+
155
+ # Subscription
156
+ end
157
+
158
+ attr_accessor :definition, :state, :client, :watcher,
159
+ :poll_interval, :previous_poll_time, :last_poll_time
160
+
161
+ def initialize(definition, state = nil, client = nil)
162
+ @definition = definition
163
+ @state = state || State.new
164
+ @client = @watcher = client || Client.new
165
+ @poll_interval = definition.fetch('poll_interval', 15)
166
+ process_definition(definition)
167
+ end
168
+
169
+ def logger
170
+ client.logger
171
+ end
172
+
173
+ def path
174
+ definition['path']
175
+ end
176
+
177
+ def process_definition(watch_folder_def)
178
+ state.subscription = subscription_get_or_create(watch_folder_def)
179
+ end
180
+
181
+ def subscription_get_or_create(watch_folder_def)
182
+ Subscription.get_or_create(client, watch_folder_def)
183
+ end
184
+
185
+ def process_stable_entry(entry)
186
+ logger.debug { "Stable Entry Detected: #{entry.stat}" }
187
+ end
188
+
189
+ def events(from, to)
190
+ state.subscription.client.subscription_snapshot_differential(subscription_id: state.subscription['identifier'],
191
+ from: from,
192
+ to: to)
193
+ end
194
+
195
+ def poll
196
+ subscription = state.subscription
197
+ subscription.resubscribe
198
+ previous_snapshot_version = subscription.snapshots.keys.last
199
+
200
+ # Delete other snapshots
201
+ subscription.snapshots.delete_if { |p, e| p != previous_snapshot_version } if previous_snapshot_version
202
+
203
+ snapshot = subscription.snapshot_create
204
+ @previous_poll_time = @last_poll_time
205
+ @last_poll_time = Time.now
206
+
207
+ no_change = snapshot.version == previous_snapshot_version
208
+ if !no_change
209
+ events = snapshot.differential(previous_snapshot_version)
210
+ logger.debug { "Events: #{events}" }
211
+ end
212
+
213
+ _known_path_map = state.known_path_map.dup
214
+
215
+ logger.debug { "Subscription ID: #{subscription['identifier']}" }
216
+ logger.debug { "Previous Snapshot: #{previous_snapshot_version}" }
217
+ logger.debug { "Current Snapshot: #{snapshot.version}" }
218
+ logger.debug { "Known Paths: #{_known_path_map.keys}" }
219
+
220
+ current_path_map = snapshot.entries_by_path
221
+ current_paths = current_path_map.keys
222
+
223
+ new_path_map = current_path_map.dup
224
+
225
+ deleted_path_map = {}
226
+ stable_path_map = {}
227
+ unstable_path_map = {}
228
+ _known_path_map.delete_if do |p, e|
229
+ deleted = !no_change && !current_paths.include?(p)
230
+ if deleted
231
+ logger.debug { "DELETED '#{p}'" }
232
+ deleted_path_map[p] = e
233
+ else
234
+ new_path_map.delete(p) # The file is not new, so remove it from the new list
235
+
236
+ new_entry = current_path_map[p]
237
+ previous_stat = e.stat.values_at('mtime', 'size')
238
+ new_stat = new_entry.stat.values_at('mtime', 'size')
239
+
240
+ if no_change || new_stat === previous_stat
241
+ logger.debug { "UNCHANGED '#{p}' '#{new_stat}' == '#{previous_stat}'" }
242
+ stable_poll_count = e[:stable_poll_count] || 0
243
+ stable_poll_count += 1
244
+ e[:stable_poll_count] = stable_poll_count
245
+ stable_path_map[p] = e
246
+ else
247
+ logger.debug { "MODIFIED '#{p}' '#{new_stat}' != '#{previous_stat}'" }
248
+ e[:stable_poll_count] = 0
249
+ unstable_path_map[p] = new_entry
250
+ end
251
+ end
252
+ deleted
253
+ end
254
+
255
+ new_path_map.each do |p, e|
256
+ logger.debug { "CREATED '#{p}' #{e.stat.values_at('mtime', 'size')}" }
257
+ e[:stable_poll_count] = 0
258
+ unstable_path_map[p] = e.dup
259
+ end
260
+
261
+ _known_path_map.merge! stable_path_map
262
+ _known_path_map.merge! unstable_path_map unless no_change
263
+ state.known_path_map = _known_path_map
264
+ state.details = {
265
+ previous_snapshot_version: previous_snapshot_version,
266
+ maps: {
267
+ new: new_path_map,
268
+ deleted: deleted_path_map,
269
+ stable: stable_path_map,
270
+ unstable: unstable_path_map
271
+ }
272
+ }
273
+
274
+ logger.debug { "Known Paths: #{_known_path_map.keys}" }
275
+ logger.debug { "New Paths: #{new_path_map.map { |p, e| "#{p} #{e.stat}" }}" }
276
+
277
+ logger.debug { "Deleted Paths: #{deleted_path_map.keys}" }
278
+ logger.debug { "Unstable Paths: #{unstable_path_map.keys}" }
279
+ logger.debug { "Stable Paths: #{stable_path_map.map { |p, e| [p, e[:stable_poll_count]] }}" }
280
+
281
+ yield self if block_given?
282
+
283
+ end
284
+
285
+ def self.process_watch_folder_def(watch_folder_def)
286
+ new(watch_folder_def)
287
+ end
288
+
289
+ def self.process_watch_folder_defs(watch_folder_defs)
290
+
291
+
292
+ end
293
+
294
+ def self.process_watch_folder(watch_folder, &block)
295
+ watch_folder.poll(&block) if (Time.now - watch_folder.last_poll_time) >= watch_folder.poll_interval
296
+ end
297
+
298
+ def self.process_watch_folders(watch_folders, &block)
299
+ watch_folders.each { |watch_folder| process_watch_folder(watch_folder, &block) }
300
+ end
301
+
302
+ def self.run_once(watch_folders, &block)
303
+ process_watch_folders(watch_folders, &block)
304
+ end
305
+
306
+ def self.run(watch_folders, poll_interval = 15, &block)
307
+ puts 'Starting...'
308
+ loop do
309
+ begin
310
+ run_once(watch_folders, &block)
311
+ sleep 1
312
+ rescue SystemExit, Interrupt
313
+ break
314
+ end
315
+ end
316
+ puts 'Exiting...'
317
+ end
318
+
319
+ end
320
+ end
321
+ end
322
+ end
@@ -12,7 +12,7 @@ module Envoi
12
12
 
13
13
  def initialize(args = { })
14
14
  @initial_args = args.clone
15
- @config = args[:config]
15
+ @config = args[:config] || { }
16
16
  @notifiers = args[:notifiers] || [ ]
17
17
 
18
18
  @dry_run = args.fetch(:dry_run, false)
@@ -36,7 +36,13 @@ module Envoi
36
36
  return if @notifiers.empty?
37
37
  args[:level] ||= :info
38
38
  args[:message] ||= message
39
- @notifiers.each { |notifier| notifier.notify(args) }
39
+ @notifiers.each do |notifier|
40
+ begin
41
+ notifier.notify(args) rescue nil
42
+ rescue => e
43
+ logger.warn { "Notification Failed: #{e.message} "}
44
+ end
45
+ end
40
46
  end
41
47
 
42
48
  # @param [Hash] args {}
@@ -48,13 +54,17 @@ module Envoi
48
54
  end
49
55
  end
50
56
 
57
+ # @param [String] command
58
+ # @param [Boolean] dry_run
59
+ # @return [Hash]
51
60
  def shell_execute(command, dry_run = @dry_run)
52
61
  if dry_run
53
62
  logger.debug { "Skipping Execution of Command: '#{command}' " }
54
- return
63
+ return { }
55
64
  end
56
65
  logger.debug { "Executing Command: '#{command}'" }
57
66
 
67
+ success = false
58
68
  Open3.popen3(command) do |stdin, stdout, stderr, thread|
59
69
  # stdin.sync = true
60
70
  # stdout.sync = true
@@ -65,36 +75,46 @@ module Envoi
65
75
  output << stdout.read #rescue nil
66
76
  output << stderr.read # rescue nil
67
77
  unless output.empty?
68
- print output
78
+ logger.debug { output }
69
79
  output.clear
70
80
  end
71
81
  end
82
+
83
+ success = thread.value == 0 ? true : false
72
84
  end
85
+
86
+ { success: success }
73
87
  end
74
88
 
75
- def self.load_from_config_file(args)
76
- config_file_path = args[:config_file_path]
77
- config_file_path = config_file_path.find { |v| File.exists?(v) } if config_file_path.is_a?(Array)
78
- abort("Missing Config File. '#{config_file_path}'") unless config_file_path && !config_file_path.empty? && File.exists?(config_file_path)
89
+ def self.load_config_from_file(args)
90
+ config_file_paths = args[:config_file_path]
91
+ config_file_path = [*config_file_paths].find { |v| File.exists?(File.expand_path(v)) }
92
+ raise "Configuration file not found. Searched '#{config_file_paths}'" unless config_file_path && !config_file_path.empty? && File.exists?(config_file_path)
79
93
 
80
94
  begin
81
95
  config = JSON.parse(File.read(config_file_path))
82
96
  rescue => e
83
- abort("Config File Failed to Load. '#{$!}'")
97
+ raise "Failed to Load Config File. '#{config_file_path}' '#{e.message}'"
84
98
  end
85
- args[:config] = config
86
-
87
- self.new(args.merge({ :config => config }))
99
+ config
88
100
  end
89
101
 
90
- def self.load_from_config_service(args)
102
+ def self.load_config_from_service(args)
91
103
  args_out = { }
92
104
  args_out[:app_id] = args[:config_service_app_id]
93
105
  args_out[:token] = args[:config_service_app_token]
94
106
  args_out[:api_url] = args[:config_service_app_url]
95
107
  config = Envoi::Mam::Agent::ConfigServiceClient.config_get(args_out)
96
- args[:config] = config
108
+ config
109
+ end
97
110
 
111
+ def self.load_from_config_file(args)
112
+ config = load_config_from_file(args)
113
+ self.new(args.merge({ :config => config }))
114
+ end
115
+
116
+ def self.load_from_config_service(args)
117
+ config = load_config_from_service(args)
98
118
  self.new(args.merge({ :config => config }))
99
119
  end
100
120
 
@@ -6,11 +6,11 @@ module Envoi
6
6
  module Mam
7
7
  class Agent
8
8
 
9
- class Cli
9
+ class CLI
10
10
 
11
11
  CONFIG_FILE_PATHS = [
12
- "./envoi-mam-agent-config.json",
13
- "~/envoi-mam-agent-config.json",
12
+ './envoi-mam-agent-config.json',
13
+ '~/envoi-mam-agent-config.json',
14
14
  ]
15
15
  CONFIG_FILE_PATHS.map! { |v| File.expand_path(v) }
16
16
 
@@ -23,33 +23,43 @@
23
23
  # # Envoi
24
24
  # end
25
25
 
26
- def usage
27
- command_list_str = ''
28
- Dir.glob(File.join(@commands_dir, "*#{@command_ext}")).each do |fn|
29
- command_list_str += "\n\t - #{File.basename(fn, @command_ext)}"
26
+ @commands_dir = File.join(File.dirname(__FILE__), 'commands')
27
+ @command_ext = '.rb'
28
+
29
+ @command_list_str = ''
30
+ @commands = [ ]
31
+ @command_aliases = { }
32
+
33
+ def build_command_aliases(commands_dir = @commands_dir)
34
+ Dir.glob(File.join(commands_dir, "*.rb")).each do |fn|
35
+ command_file_name = File.basename(fn)
36
+ command_name = File.basename(command_file_name, '.*')
37
+ command_alias = command_name.tr('-_', '')
38
+ @commands << command_name
39
+ @command_aliases[command_alias] = command_name
40
+ @command_list_str += "\n\t - #{command_name}"
30
41
  end
42
+ end
43
+
44
+ def usage
31
45
 
32
46
  <<-EOT
33
47
  Usage: #{File.basename($0)} COMMAND [ARGS]
34
48
 
35
49
  Available Commands:
36
- #{command_list_str}
50
+ #{@command_list_str}
37
51
 
38
52
  All commands can be run with -h (or --help) for more information.
39
53
  EOT
40
54
  end
41
55
 
42
- @commands_dir = File.join(File.dirname(__FILE__), 'commands')
43
- @command_ext = '.rb'
44
-
45
- command_aliases = {
46
- }
47
56
 
57
+ build_command_aliases
48
58
  if ARGV.empty?
49
59
  should_show_usage = true
50
60
  else
51
- command_name = ARGV[0].clone
52
- command_name = command_aliases[command_name] || command_name
61
+ command_name = ARGV.first.dup
62
+ command_name = @command_aliases[command_name] || command_name
53
63
  should_show_usage = %w(-h --help).include?(command_name.downcase)
54
64
  end
55
65
  if should_show_usage
@@ -70,14 +80,14 @@ end
70
80
 
71
81
 
72
82
  # Execute the command if it is in object form, otherwise it's just a script and executed when we required it
73
- if Envoi::Mam::Agent::Cli.const_defined?('Commands')
83
+ if Envoi::Mam::Agent::CLI.const_defined?('Commands')
74
84
  command_class_name = command_name.to_s.capitalize
75
- if Envoi::Mam::Agent::Cli::Commands.const_defined?(command_class_name)
76
- command_object = Envoi::Mam::Agent::Cli::Commands.const_get( command_class_name.capitalize ) rescue nil
85
+ if Envoi::Mam::Agent::CLI::Commands.const_defined?(command_class_name)
86
+ command_object = Envoi::Mam::Agent::CLI::Commands.const_get(command_class_name.capitalize ) rescue nil
77
87
  end
78
88
  command_object ||= begin
79
89
  command_class_name = command_class_name.upcase
80
- Envoi::Mam::Agent::Cli::Commands.const_defined?(command_class_name) ? Envoi::Mam::Agent::Cli::Commands.const_get( command_class_name ) : false
90
+ Envoi::Mam::Agent::CLI::Commands.const_defined?(command_class_name) ? Envoi::Mam::Agent::CLI::Commands.const_get(command_class_name ) : false
81
91
  end
82
92
  if command_object
83
93
  command_instance = command_object.new if command_object.respond_to?(:new)