palo_alto 0.3.1 → 0.3.2

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.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PaloAlto
4
- VERSION = '0.3.1'
4
+ VERSION = '0.3.2'
5
5
  end
data/lib/palo_alto.rb CHANGED
@@ -170,14 +170,38 @@ module PaloAlto
170
170
  attr_accessor :host, :username, :auth_key, :verify_ssl, :debug, :timeout
171
171
 
172
172
  def pretty_print_instance_variables
173
- super - [:@password, :@subclasses, :@subclasses, :@expression, :@arguments]
173
+ super - [:@password, :@subclasses, :@subclasses, :@expression, :@arguments, :@cache]
174
174
  end
175
175
 
176
- def execute(payload, skip_authentication: false)
176
+ def execute(payload, skip_authentication: false, skip_cache: false)
177
177
  if !auth_key && !skip_authentication
178
178
  get_auth_key
179
179
  end
180
180
 
181
+ if payload[:type] == 'config' && !skip_cache
182
+ if payload[:action] == 'get'
183
+ start_time = Time.now
184
+ @cache.each do |cached_xpath, cache|
185
+ search_xpath = payload[:xpath].sub('/descendant::device-group[1]/', '/device-group/')
186
+ next unless search_xpath.start_with?(cached_xpath)
187
+
188
+ remove = cached_xpath.split('/')[1...-1].join('/').length
189
+ new_xpath = 'response/result/' + search_xpath[(remove+2)..]
190
+
191
+ results = cache.xpath(new_xpath)
192
+ xml = Nokogiri.parse("<?xml version=\"1.0\"?><response><result>#{results.to_s}</result></response>")
193
+
194
+ if debug.include?(:statistics)
195
+ warn "Elapsed for parsing cache: #{Time.now - start_time} seconds"
196
+ end
197
+
198
+ return xml
199
+ end
200
+ elsif !@keep_cache_on_edit
201
+ @cache = {}
202
+ end
203
+ end
204
+
181
205
  retried = false
182
206
  begin
183
207
  # configure options for the request
@@ -198,7 +222,7 @@ module PaloAlto
198
222
  start_time = Time.now
199
223
  text = Helpers::Rest.make_request(options)
200
224
  if debug.include?(:statistics)
201
- warn "Elapsed for API call #{payload[:type]}/#{payload[:action] || '(unknown action)'} on #{host}: #{Time.now - start_time} seconds"
225
+ warn "Elapsed for API call #{payload[:type]}/#{payload[:action] || '(unknown action)'} on #{host}: #{Time.now - start_time} seconds, #{text.length} bytes"
202
226
  end
203
227
 
204
228
  warn "received: #{Time.now}\n#{text}\n" if debug.include?(:received)
@@ -231,14 +255,39 @@ module PaloAlto
231
255
  end
232
256
  end
233
257
 
234
- def commit!(all: false, device_groups: nil, templates: nil, wait_for_completion: true, wait: 5, timeout: 480)
258
+ def clear_cache!
259
+ @cache = {}
260
+ @keep_cache_on_edit = nil
261
+ end
262
+
263
+ def keep_cache_on_edit!
264
+ @keep_cache_on_edit = true
265
+ end
266
+
267
+ def cache!(xpath)
268
+ cached_xpath = xpath.is_a?(String) ? xpath : xpath.to_xpath
269
+
270
+ payload = {
271
+ type: 'config',
272
+ action: 'get',
273
+ xpath: cached_xpath
274
+ }
275
+
276
+ @cache[cached_xpath] = execute(payload, skip_cache: true)
277
+ true
278
+ end
279
+
280
+ def commit!(all: false, device_groups: nil, templates: nil,
281
+ admins: [username],
282
+ raw_result: false,
283
+ wait_for_completion: true, wait: 5, timeout: 480)
235
284
  return nil if device_groups.is_a?(Array) && device_groups.empty? && templates.is_a?(Array) && templates.empty?
236
285
 
237
286
  cmd = if all
238
287
  'commit'
239
288
  else
240
289
  { commit: { partial: [
241
- { 'admin': [username] },
290
+ { 'admin': admins },
242
291
  if device_groups
243
292
  device_groups.empty? ? 'no-device-group' : { 'device-group': device_groups }
244
293
  end,
@@ -256,8 +305,9 @@ module PaloAlto
256
305
  end
257
306
  result = op.execute(cmd)
258
307
 
259
- job_id = result.at_xpath('response/result/job')&.text
308
+ return result if raw_result
260
309
 
310
+ job_id = result.at_xpath('response/result/job')&.text
261
311
  return result unless job_id && wait_for_completion
262
312
 
263
313
  wait_for_job_completion(job_id, wait: wait, timeout: timeout) if job_id
@@ -294,6 +344,7 @@ module PaloAlto
294
344
 
295
345
  # will execute block if given and unlock afterwards. returns false if lock could not be aquired
296
346
  def lock(area:, comment: nil, type: nil, location: nil)
347
+ raise MalformedCommandException, 'No type specified' if location && !type
297
348
  if block_given?
298
349
  return false unless lock(area: area, comment: comment, type: type, location: location)
299
350
 
@@ -309,7 +360,8 @@ module PaloAlto
309
360
  op.execute(cmd, type: type, location: location)
310
361
  true
311
362
  rescue PaloAlto::InternalErrorException => e
312
- return true if e.message.start_with?('You already own a config lock for scope ')
363
+ return true if e.message.start_with?('You already own a config lock for scope ') ||
364
+ e.message == "Config for scope shared is currently locked by #{username}"
313
365
 
314
366
  false
315
367
  end
@@ -337,9 +389,9 @@ module PaloAlto
337
389
  end
338
390
  end
339
391
 
340
- def check_for_changes(usernames: [username])
341
- cmd = if usernames
342
- { show: { config: { list: { 'change-summary': { partial: { admin: usernames } } } } } }
392
+ def check_for_changes(admins: [username])
393
+ cmd = if admins
394
+ { show: { config: { list: { 'change-summary': { partial: { admin: admins } } } } } }
343
395
  else
344
396
  { show: { config: { list: 'change-summary' } } }
345
397
  end
@@ -350,6 +402,42 @@ module PaloAlto
350
402
  }
351
403
  end
352
404
 
405
+ # returns nil if job isn't finished yet, otherwise the job result
406
+ def query_and_parse_job(job_id)
407
+ cmd = { show: { jobs: { id: job_id } } }
408
+ result = op.execute(cmd)
409
+ status = result.at_xpath('response/result/job/status')&.text
410
+ return result unless %w[ACT PEND].include?(status)
411
+
412
+ nil
413
+ rescue => e
414
+ warn [:job_query_error, e].inspect
415
+ false
416
+ end
417
+
418
+ # returns true if successful
419
+ # returns nil if not completed yet
420
+ # otherwise returns the error
421
+ def commit_successful?(commit_result)
422
+ if commit_result.at_xpath('response/msg')&.text&.start_with?('The result of this commit would be the same as the previous commit queued/processed') ||
423
+ commit_result.at_xpath('response/msg')&.text == 'There are no changes to commit.'
424
+ return true
425
+ end
426
+
427
+ job_id = commit_result.at_xpath('response/result/job')&.text
428
+ unless job_id
429
+ warn [:no_job_id, result].inspect
430
+ return false
431
+ end
432
+
433
+ job_result = query_and_parse_job(job_id)
434
+ return job_result if !job_result # can be either nil or false (errored)
435
+
436
+ return true if job_result.xpath('response/result/job/details/line').text&.include?('Configuration committed successfully')
437
+
438
+ job_result
439
+ end
440
+
353
441
  def wait_for_job_completion(job_id, wait: 5, timeout: 600)
354
442
  cmd = { show: { jobs: { id: job_id } } }
355
443
  start = Time.now
@@ -406,6 +494,8 @@ module PaloAlto
406
494
 
407
495
  @subclasses = {}
408
496
 
497
+ @cache = {}
498
+
409
499
  # xpath
410
500
  @expression = :root
411
501
  @arguments = [Expression.new(:this_node), []]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: palo_alto
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Roesner
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-09-09 00:00:00.000000000 Z
11
+ date: 2023-01-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri