palo_alto 0.1.3 → 0.1.7

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad314eed2154013cd0dd81166c795481debf4762a5d5b5b47f0943d8db612f98
4
- data.tar.gz: 02ba2cb368a2bae0a3bc7c4c121d79628b600df89e70f837a613871e1db041c6
3
+ metadata.gz: f3f5565d4ace0fcd1e96290bcd2b5e2d1ffa726b53fd7fe691497f7a2c137d42
4
+ data.tar.gz: 9e8e63ab0508abf76cc5fdf13e6827067aa42100f6beb996ef16b1256dd96f5b
5
5
  SHA512:
6
- metadata.gz: a63062907d61012d713da785791526c167370d39248190a356f17f3bc4392ba3010e9ace8a1bd25fd693914bca6440a97ae03c95ce7d81296c275157299e370c
7
- data.tar.gz: c752bc2dce7f2e325708d4284d74a0c167ae4b623569dd4254a93636fefbf2df0e2f72a247ac4207791c4edcf4c05ef1a0d10d72b95acb703a554d62e6c2d9bb
6
+ metadata.gz: 59e3ee0f6f425554cf6dd10ac49310cdc4ebba3ec24ad9cf3914f7e5a6c465a386ccfe8cae77041691ddbc157c281c66aaee84b49f617057bcd750cd81e7c0e3
7
+ data.tar.gz: ad822a803c73d950cfdf0e428c41ee47b7fc3c3dd8b3fb631cecf2cf19c4b07e31d120fe0468cf25bed0ad250b859e7ee3d6ce7467a7c9adf7254a9697d7e1fa
@@ -1,4 +1,4 @@
1
- # generated: 2021-08-27 17:33:49 +0200
1
+ # generated: 2021-10-19 00:56:02 +0200
2
2
  require 'openssl'
3
3
  require 'nokogiri'
4
4
 
@@ -268,7 +268,11 @@ module PaloAlto
268
268
  end
269
269
 
270
270
  def binary_operator(name, left, right)
271
- "#{left}#{name}#{right}".gsub('./@', '@')
271
+ if %w(and or).include?(name)
272
+ "(#{left} #{name} #{right})".gsub('./@', '@')
273
+ else
274
+ "#{left}#{name}#{right}".gsub('./@', '@')
275
+ end
272
276
  end
273
277
 
274
278
  def root(current, element_names)
@@ -425,7 +429,7 @@ module PaloAlto
425
429
  start_time=Time.now
426
430
  result = self.parent_instance.dup.create!.clear!.external_set(data.xpath('//response/result').first)
427
431
  if XML.debug.include?(:statistics)
428
- puts "Elapsed for parsing #{result.length} results: #{Time.now-start_time} seconds"
432
+ warn "Elapsed for parsing #{result.length} results: #{Time.now-start_time} seconds"
429
433
  end
430
434
  result
431
435
  end
@@ -436,7 +440,7 @@ module PaloAlto
436
440
  self
437
441
  end
438
442
 
439
- def get(ignore_empty_result: false, xpath: self.to_xpath)
443
+ def get(ignore_empty_result: false, xpath: self.to_xpath, return_only: false)
440
444
  if self.class.superclass == ArrayConfigClass && !@selector
441
445
  raise(InvalidCommandException, "Please use 'get_all' here")
442
446
  end
@@ -454,23 +458,28 @@ module PaloAlto
454
458
  if ignore_empty_result==false
455
459
  raise(ObjectNotPresentException, "empty result: #{payload.inspect}")
456
460
  end
461
+ end
462
+
463
+ if return_only
464
+ data.xpath('//response/result/*')
457
465
  else
458
- #self.parent_instance.dup.create!.clear!.external_set(data.xpath('//response/result').first).first
459
466
  @create_children=true
460
467
  n = data.xpath('//response/result/*')
468
+ if n.any?
469
+ clear!
470
+ external_set(n.first)
461
471
 
462
- clear!
463
- external_set(n.first)
464
-
465
- if is_a?(ArrayConfigClass)
466
- primary_key = get_primary_key(n.first.attribute_nodes, self.class.props)
467
- set_array_class_attributes(n.first, primary_key) # primary key, api_attributes
472
+ if is_a?(ArrayConfigClass)
473
+ primary_key = get_primary_key(n.first.attribute_nodes, self.class.props)
474
+ set_array_class_attributes(n.first, primary_key) # primary key, api_attributes
475
+ end
476
+ end
477
+ self
478
+ end.tap do
479
+ if XML.debug.include?(:statistics)
480
+ warn "Elapsed for parsing: #{Time.now-start_time} seconds"
468
481
  end
469
482
  end
470
- if XML.debug.include?(:statistics)
471
- puts "Elapsed for parsing: #{Time.now-start_time} seconds"
472
- end
473
- self
474
483
  end
475
484
 
476
485
  def get_primary_key(attribute_nodes, props)
@@ -526,9 +535,9 @@ module PaloAlto
526
535
  when 'bool'
527
536
  return true if ['yes', true].include?(value)
528
537
  return false if ['no', false].include?(value)
529
- raise ArgumentError, 'Not bool: ' + value.inspect
538
+ raise ArgumentError, "Not bool: #{value.inspect}"
530
539
  when 'string', 'ipdiscontmask', 'iprangespec', 'ipspec', 'rangelistspec'
531
- raise(ArgumentError, 'Not string') unless value.is_a?(String)
540
+ raise(ArgumentError, "Not string: #{value.inspect}") unless value.is_a?(String)
532
541
  if prop_arr['regex']
533
542
  raise ArgumentError, "Not matching regex: #{value.inspect} (#{prop_arr["regex"].inspect})" unless value.match(prop_arr["regex"])
534
543
  end
@@ -662,28 +671,34 @@ module PaloAlto
662
671
  elsif @external_values.has_key?(prop)
663
672
  return @external_values[prop]
664
673
  elsif my_prop.has_key?("default") && include_defaults
665
- return enforce_type(my_prop, my_prop['default'])
674
+ return enforce_types(my_prop, my_prop['default'])
666
675
  else
667
676
  return nil
668
677
  end
669
678
  end
670
679
 
671
- def prop_set(prop, value)
672
- my_prop = self.class.props[prop] or raise(InternalErrorException, "Unknown attribute for #{self.class}: #{prop}")
680
+ def enforce_types(prop_arr, values)
681
+ return if values.nil?
673
682
 
674
- if has_multiple_values? && value.is_a?(String)
675
- value = value.split(/\s+/)
683
+ if has_multiple_values? && values.is_a?(String)
684
+ values = values.split(/\s+/)
676
685
  end
677
686
 
678
- if value.is_a?(Array)
679
- @values[prop] = value.map{|v| enforce_type(my_prop, v)}
680
- elsif value.nil?
681
- @values[prop] = nil
687
+ if values.is_a?(Array) && has_multiple_values?
688
+ values.map{|v| enforce_type(prop_arr, v)}
689
+ elsif !has_multiple_values?
690
+ enforce_type(prop_arr, values)
682
691
  else
683
- @values[prop] = enforce_type(my_prop, value)
692
+ raise(ArgumentError, 'Needs to be Array but is not, or vice versa')
684
693
  end
685
694
  end
686
695
 
696
+ def prop_set(prop, value)
697
+ my_prop = self.class.props[prop] or raise(InternalErrorException, "Unknown attribute for #{self.class}: #{prop}")
698
+
699
+ @values[prop] = enforce_types(my_prop, value)
700
+ end
701
+
687
702
  def to_xml(changed_only:, full_tree:, include_root: )
688
703
  builder = Nokogiri::XML::Builder.new{|xml|
689
704
  xml.send(self._section, (self.selector rescue nil)) {
@@ -768,23 +783,25 @@ module PaloAlto
768
783
 
769
784
  def set_xpath_from_selector(selector: @selector)
770
785
  xpath = self.parent_instance.child(_section)
771
- k,v=selector.first
786
+ k, v = selector.first
772
787
  obj = xpath.where(PaloAlto.xpath_attr(k.to_sym) == v)
773
788
 
774
789
  @expression = obj.expression
775
790
  @arguments = obj.arguments
776
791
  end
777
792
 
778
- def rename!(new_name)
793
+ def rename!(new_name, internal_only: false)
779
794
  # https://docs.paloaltonetworks.com/pan-os/10-1/pan-os-panorama-api/pan-os-xml-api-request-types/configuration-api/rename-configuration.html
780
- payload = {
781
- type: 'config',
782
- action: 'rename',
783
- xpath: self.to_xpath,
784
- newname: new_name
785
- }
795
+ unless internal_only
796
+ payload = {
797
+ type: 'config',
798
+ action: 'rename',
799
+ xpath: self.to_xpath,
800
+ newname: new_name
801
+ }
786
802
 
787
- result = XML.execute(payload)
803
+ result = XML.execute(payload)
804
+ end
788
805
 
789
806
  # now update also the internal value to the new name
790
807
  self.selector.transform_values!{new_name}
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PaloAlto
4
- VERSION = '0.1.3'
4
+ VERSION = '0.1.7'
5
5
  end
data/lib/palo_alto.rb CHANGED
@@ -197,7 +197,11 @@ module PaloAlto
197
197
  begin
198
198
  Helpers::Rest.execute(payload, headers: {'X-PAN-KEY': self.auth_key})
199
199
  rescue TemporaryException => e
200
- unless retried
200
+ dont_continue_at = [
201
+ 'Partial revert is not allowed. Full system commit must be completed.',
202
+ 'Config for scope '
203
+ ]
204
+ unless retried || dont_continue_at.any? { |x| e.message.start_with?(x) }
201
205
  if XML.debug.include?(:warnings)
202
206
  warn "Got error #{e.inspect}; retrying"
203
207
  end
@@ -213,12 +217,15 @@ module PaloAlto
213
217
  end
214
218
  end
215
219
 
216
- def commit!(all: false)
220
+ def commit!(all: false, device_groups: nil, wait_for_completion: true)
221
+ return nil if device_groups.is_a?(Array) && device_groups.empty?
222
+
217
223
  op = if all
218
224
  'commit'
219
225
  else
220
226
  { commit: { partial: [
221
227
  { 'admin': [XML.username] },
228
+ device_groups ? {'device-group': device_groups } : nil,
222
229
  'no-template',
223
230
  'no-template-stack',
224
231
  'no-log-collector',
@@ -227,9 +234,102 @@ module PaloAlto
227
234
  'no-wildfire-appliance-cluster',
228
235
  { 'device-and-network': 'excluded' },
229
236
  { 'shared-object': 'excluded' }
230
- ] } }
237
+ ].compact } }
231
238
  end
232
- Op.new.execute(op)
239
+ Op.new.execute(op).tap do |result|
240
+ if wait_for_completion
241
+ job_id = result.at_xpath('response/result/job')&.text
242
+ wait_for_job_completion(job_id) if job_id
243
+ end
244
+ end
245
+ end
246
+
247
+ def full_commit_required?
248
+ result = Op.new.execute({check: 'full-commit-required'})
249
+ return true unless result.at_xpath('response/result').text == 'no'
250
+
251
+ false
252
+ end
253
+
254
+ def primary_active?
255
+ cmd = {show: {'high-availability': 'state'}}
256
+ state = Op.new.execute(cmd)
257
+ state.at_xpath("response/result/local-info/state").text == "primary-active"
258
+ end
259
+
260
+ # area: config, commit
261
+ def show_locks(area:)
262
+ cmd = {show: "#{area}-locks"}
263
+ ret = Op.new.execute(cmd)
264
+ ret.xpath("response/result/#{area}-locks/entry").map do |lock|
265
+ comment = lock.at_xpath('comment').inner_text
266
+ location = lock.at_xpath('name').inner_text
267
+ {
268
+ name: lock.attribute('name').value,
269
+ location: location == 'shared' ? nil : location,
270
+ type: lock.at_xpath('type').inner_text,
271
+ comment: comment == '(null)' ? nil : comment
272
+ }
273
+ end
274
+ end
275
+
276
+ # will execute block if given and unlock afterwards. returns false if lock could not be aquired
277
+ def lock(area:, comment: nil, type: nil, location: nil)
278
+ if block_given?
279
+ if lock(area: area, comment: comment, type: type, location: location)
280
+ begin
281
+ return yield
282
+ ensure
283
+ unlock(area: area, type: type, location: location)
284
+ end
285
+ else
286
+ return false
287
+ end
288
+ end
289
+
290
+ begin
291
+ cmd = {request: {"#{area}-lock": {add: {comment: comment || '(null)' }}}}
292
+ Op.new.execute(cmd, get_extra_argument(type: type, location: location))
293
+ true
294
+ rescue PaloAlto::InternalErrorException
295
+ false
296
+ end
297
+ end
298
+
299
+ def unlock(area:, type: nil, location: nil)
300
+ begin
301
+ cmd = {request: {"#{area}-lock": 'remove'}}
302
+ Op.new.execute(cmd, get_extra_argument(type: type, location: location))
303
+ rescue PaloAlto::InternalErrorException
304
+ return false
305
+ end
306
+ true
307
+ end
308
+
309
+ def remove_all_locks
310
+ %w(config commit).each do |area|
311
+ show_locks(area: area).each {|lock|
312
+ unlock(area: area, type: lock[:type], location: lock[:location])
313
+ }
314
+ end
315
+ end
316
+
317
+ def check_for_changes(usernames: [XML.username])
318
+ result = Op.new.execute({show: {config: {list: {'change-summary': {partial: {admin: usernames}}}}}})
319
+ result.xpath('response/result/summary/device-group/member').map(&:inner_text)
320
+ end
321
+
322
+ def wait_for_job_completion(job_id, wait: 5, timeout: 300)
323
+ cmd = {show: {jobs: {id: job_id}}}
324
+ start = Time.now
325
+ begin
326
+ result = Op.new.execute(cmd)
327
+ unless result.at_xpath('response/result/job/status')&.text=='ACT'
328
+ return result
329
+ end
330
+ sleep wait
331
+ end while start+timeout > Time.now
332
+ return false
233
333
  end
234
334
 
235
335
  def revert!(all: false)
@@ -284,5 +384,17 @@ module PaloAlto
284
384
  xml_data = Helpers::Rest.execute(payload)
285
385
  self.auth_key = xml_data.xpath('//response/result/key')[0].content
286
386
  end
387
+
388
+ private
389
+
390
+ # used to limit an op command to a specifc dg/template
391
+ def get_extra_argument(type:, location:)
392
+ case type
393
+ when 'dg' then {vsys: location}
394
+ when 'tpl' then raise
395
+ else {}
396
+ end
397
+ end
398
+
287
399
  end
288
400
  end
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.1.3
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Roesner
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-09-08 00:00:00.000000000 Z
11
+ date: 2021-10-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri