palo_alto 0.5.0 → 0.5.1

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
  SHA256:
3
- metadata.gz: 99770a923e262b0fbbf0fbc9a24008a2d31dcd863e59db9c583d15337679e0c8
4
- data.tar.gz: e1387ace8b607aebebc4d11c73c82dc3c112a6ddb2ceb1744fcad50b0233322c
3
+ metadata.gz: 35c89839bc38cd0398a88bd1c12c701b27e86de98bd1012a6c9d939898e9982a
4
+ data.tar.gz: 4c7e1ac46cf17e7d0780e768c2923dfa457e8cda93b8b3e714057e5909c3e764
5
5
  SHA512:
6
- metadata.gz: 3ca35a0bb12cf88ab772ddd57f8aac60f4d913b38f844b6dbd3b5c9cf6d7cdfe028c5d98506df597075908ca34e60eecfe8b3e9333d1d3eec669f19610b9bf24
7
- data.tar.gz: 89f6c2e888e1f2093aa0b2d1563de4b3e23f13ed1d3b48ef41dcbf3b25b4948acea6d399f2dea7b231636ec2417077ebae4286eb47b2e77425987f5bd1c18cb3
6
+ metadata.gz: 351d244c00165c18d7d94d1c0e35d820db16f6cb586d613fe0d682df00fd1f49c68846548effa82d6568521fac0cfff31c62135bd8dfc81bb7b96f8ad3181d02
7
+ data.tar.gz: 65bb8bf61772a2630edb8354f9eb6098e25aaef0c420413bb00995f0eab9f6b4ab8c7a4d964a87ecd8921b9b53da9ad133ad249872e33badd59a0957a74db04e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,5 @@
1
+ Version 0.5.1: Breaking changes for op commands, to be able to build more complex scenarios
2
+ Version 0.5.0: Update schema for Panorama 11.0
1
3
  Version 0.4.1: Update schema for Panorama 10.2 for op commands
2
4
  Version 0.4.0: Update schema for Panorama 10.2 for config
3
5
  Version 0.3.0: Update schema for Panorama 10.1
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'palo_alto'
2
4
 
3
5
  client = PaloAlto::XML.new(host: 'panorama-test', username: 'admin', password: 'Admin123!',
@@ -6,11 +8,11 @@ dg = 'PLAYGROUND'
6
8
 
7
9
  # create a tag
8
10
  tag_name = 'test'
9
-
10
11
  new_tag = client.config.devices.entry(name: 'localhost.localdomain').device_group.entry(name: dg).tag.entry(name: tag_name).create!
11
12
  new_tag.color = 'color23'
12
- new_tag.push!
13
+ new_tag.set!
13
14
 
15
+ # get rules
14
16
  # filtered rules:
15
17
  # rules = client.config.devices.entry(name:'localhost.localdomain').device_group.entry(name: 'PLAYGROUND').pre_rulebase.security.rules
16
18
  # .entry{ (child(:source).child(:member).text == "Net_10.1.1.0-24").or(child(:destination).child(:member).text == 'Net_10.1.1.0-24') }
@@ -19,46 +21,60 @@ new_tag.push!
19
21
  # or:
20
22
  #
21
23
  # filter = (PaloAlto.child(:source).child(:member).text == "Net_10.1.1.0-24").or(PaloAlto.child(:destination).child(:member).text == 'Net_10.1.1.0-24')
22
- # puts filter.to_xpath
24
+ # puts filter.to_xpath # prints generated Xpath filter
23
25
  # => ./source/member/text()='Net_10.1.1.0-24'or./destination/member/text()='Net_10.1.1.0-24'
24
26
  #
25
27
  # rules = client.config.devices.entry(name:'localhost.localdomain').device_group.entry(name: 'PLAYGROUND').pre_rulebase.security.rules
26
28
  # .entry{filter}.get_all
27
- #
29
+
28
30
  # also more advanced filters are possible:
29
- # PaloAlto.not(PaloAlto.child(:'profile-setting').child(:group).child(:member) == 'IPS-Policy').and(
31
+ # filter = PaloAlto.not(PaloAlto.child(:'profile-setting').child(:group).child(:member) == 'IPS-Policy').and(
30
32
  # PaloAlto.parenthesis(
31
33
  # (PaloAlto.child(:tag).child(:member) == 'ips_enabled').or(
32
34
  # PaloAlto.child(:tag).child(:member) == 'ips_force_enabled'
33
35
  # )
34
36
  # )
35
- # ).to_xpath
36
- #
37
+ # )
38
+ # puts filter.to_xpath
37
39
  # => not(./profile-setting/group/member='IPS-Policy')and(./tag/member='ips_enabled'or./tag/member='ips_force_enabled')
38
40
 
39
41
  rules = client.config.devices.entry(name: 'localhost.localdomain').device_group.entry(name: dg).pre_rulebase.security.rules.entry{}.get_all
40
42
 
41
- rules.reject! { |rule| rule.api_attributes['loc'] != dg } # remove rules inherited from upper device groups from array
43
+ rules.select! { |rule| rule.api_attributes['loc'] == dg } # filter rules inherited from upper device groups
42
44
 
43
45
  pp rules
44
46
  pp rules.length
45
47
 
46
- pp rules.first.api_attributes # attributes like uuid and loc
47
- pp rules.first.values # values as hash
48
-
49
48
  rule = rules.first
49
+
50
+ pp rule.api_attributes # attributes like uuid and loc
51
+ pp rule.values # values as hash
52
+
50
53
  rule.tag.member = [new_tag.name]
51
54
  rule.group_tag = new_tag.name
52
55
  rule.description += '....'
53
- rule.push!
56
+ rule.edit!
54
57
 
58
+ # renaming rules
55
59
  puts rule.to_xpath
56
60
  rule.rename!('Test 1')
57
61
  puts rule.to_xpath
58
- pp rule.name
62
+ puts rule.name
59
63
 
60
- exit 0
64
+ # Bulk changes on multiple rules:
65
+ rules = client.config.devices.entry(name: 'localhost.localdomain').device_group.entry(name: dg).pre_rulebase.security.rules.get
66
+
67
+ rules.entries.each do |name, rule|
68
+ next unless rule.values.dig('profile-setting', 'group', 'member') == ['Internal-detect']
69
+
70
+ rule.profile_setting.group.member = ['Internal']
71
+ # to remove profile-setting: rule.delete_child('profile-setting')
72
+ end
73
+ puts "Pushing all rules to #{rules.to_xpath}"
74
+ rules.edit!
61
75
 
62
76
  # create a new template
63
77
  new_template = client.config.devices.entry(name: 'localhost.localdomain').template.entry(name: 'testtemplate').create!
64
- new_template.push!
78
+ new_template.set!
79
+
80
+ exit 0
data/examples/test_op.rb CHANGED
@@ -1,31 +1,33 @@
1
- require 'palo_alto'
1
+ # frozen_string_literal: true
2
2
 
3
- a = { commit: { partial: [
4
- { admin: ['admin'] },
5
- 'no-template',
6
- 'no-template-stack',
7
- 'no-log-collector',
8
- 'no-log-collector-group',
9
- 'no-wildfire-appliance',
10
- 'no-wildfire-appliance-cluster',
11
- { 'device-and-network': 'excluded' },
12
- { 'shared-object': 'excluded' }
13
- ] } }
3
+ require 'palo_alto'
4
+ load '/usr/share/panorama-api/new_op.rb'
5
+
6
+ a = { commit: { partial:
7
+ { admin: ['admin'],
8
+ 'no-template': true,
9
+ 'no-template-stack': true,
10
+ 'no-log-collector': true,
11
+ 'no-log-collector-group': true,
12
+ 'no-wildfire-appliance': true,
13
+ 'no-wildfire-appliance-cluster': true,
14
+ 'device-and-network': 'excluded',
15
+ 'shared-object': 'excluded' } } }
14
16
 
15
17
  b = { show: { devices: 'all' } }
16
18
 
17
19
  c = { revert: { config: {
18
- partial: [
19
- { admin: ['admin'] },
20
- 'no-template',
21
- 'no-template-stack',
22
- 'no-log-collector',
23
- 'no-log-collector-group',
24
- 'no-wildfire-appliance',
25
- 'no-wildfire-appliance-cluster',
26
- { 'device-and-network': 'excluded' },
27
- { 'shared-object': 'excluded' }
28
- ]
20
+ partial: {
21
+ admin: ['admin'],
22
+ 'no-template': true,
23
+ 'no-template-stack': true,
24
+ 'no-log-collector': true,
25
+ 'no-log-collector-group': true,
26
+ 'no-wildfire-appliance': true,
27
+ 'no-wildfire-appliance-cluster': true,
28
+ 'device-and-network': 'excluded',
29
+ 'shared-object': 'excluded'
30
+ }
29
31
  } } }
30
32
 
31
33
  d = { commit: nil }
@@ -44,18 +46,20 @@ k = { check: 'full-commit-required' }
44
46
 
45
47
  l = { show: { config: { 'commit-scope': { partial: { admin: ['admin'] } } } } }
46
48
 
49
+ m = { show: { config: { 'commit-scope': { partial: { admin: %w[admin1 admin2] } } } } }
50
+
47
51
  push_to_device = { 'commit-all': { 'shared-policy': { 'device-group': [{ name: 'TEST-DG' }] } } }
48
52
 
49
53
  # validate:
50
54
  p = { 'commit-all':
51
55
  {
52
- 'shared-policy': [
53
- { 'device-group': [{ name: 'PLAYGROUND' }] },
54
- { 'include-template': 'yes' },
55
- { 'merge-with-candidate-cfg': 'yes' },
56
- { 'force-template-values': 'no' },
57
- { 'validate-only': 'yes' }
58
- ]
56
+ 'shared-policy': {
57
+ 'device-group': [{ name: 'PLAYGROUND' }],
58
+ 'include-template': 'yes',
59
+ 'merge-with-candidate-cfg': 'yes',
60
+ 'force-template-values': 'no',
61
+ 'validate-only': 'yes'
62
+ }
59
63
  } }
60
64
 
61
65
  i = { show: { query: { result: { id: 10_438 } } } }
@@ -63,64 +67,39 @@ i = { show: { query: { result: { id: 10_438 } } } }
63
67
  # hit counts:
64
68
  device_group = 'PLAYGROUND'
65
69
 
66
- l = {
70
+ hc1 = {
67
71
  show: {
68
- 'rule-hit-count': [{
72
+ 'rule-hit-count': {
69
73
  'device-group': [{
70
- entry: [{
71
- name: device_group
72
- }, {
73
- 'pre-rulebase': [{
74
- entry: [{
75
- name: 'security'
76
- }, {
77
- rules: 'all'
78
- }]
79
- }]
74
+ name: device_group,
75
+ 'pre-rulebase': [{
76
+ name: 'security',
77
+ rules: ['all']
80
78
  }]
81
79
  }]
82
- }]
80
+ }
83
81
  }
84
82
  }
85
83
 
86
84
  # hit count for one rule, with more details:
87
85
  rule_name = 'Rule 27'
88
- l = {
86
+ hc2 = {
89
87
  show: {
90
- 'rule-hit-count': [{
88
+ 'rule-hit-count': {
91
89
  'device-group': [{
92
- entry: [{
93
- name: device_group
94
- }, {
95
- 'pre-rulebase': [{
96
- entry: [{
97
- name: 'security'
98
- }, {
99
- rules: {
100
- 'rule-name': [{
101
- entry: [{
102
- name: rule_name
103
- }]
104
- }]
105
- }
106
- }]
107
- }]
90
+ name: device_group,
91
+ 'pre-rulebase': [{
92
+ name: 'security',
93
+ rules: { 'rule-name': [{ name: rule_name }] }
108
94
  }]
109
95
  }]
110
- }]
96
+ }
111
97
  }
112
98
  }
113
99
 
114
100
  client = PaloAlto::XML.new(host: 'panorama-test', username: 'admin', password: 'Admin123!', debug: %i[sent received])
115
101
 
116
- # pp client.op.execute(a)
117
- # pp client.op.execute(b)
118
- # pp client.op.execute(c)
119
- pp client.op.execute(d)
120
- puts '---------------------------'
121
- pp client.op.execute(e)
122
- puts '---------------------------'
123
-
124
- # pp client.op.execute(f)
125
-
126
- pp client.op.execute(k)
102
+ [a, b, c, d, e, f, g, h, j, k, l, m, push_to_device, p, i, hc1, hc2].each do |cmd|
103
+ puts client.op.to_xml(cmd)
104
+ puts '---------------------------'
105
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- # generated: 2023-11-22 12:05:10 +0100
2
+ # generated: 2024-03-22 15:42:08 +0100
3
3
  # rubocop:disable Style/FrozenStringLiteralComment
4
4
  require 'openssl'
5
5
  require 'nokogiri'
@@ -494,9 +494,8 @@ end
494
494
  raise(ArgumentError, "Nothing matching found for #{value.inspect} (#{prop_hash.inspect})")
495
495
  end
496
496
  end
497
- def xml_builder(xml, full_tree: false)
498
- keys = self._class.props.keys
499
- keys.map do |k|
497
+ def xml_builder(xml, full_tree: false, tag_filter: nil)
498
+ self._class.props.keys.select { |key| tag_filter.nil? || tag_filter.include?(key) }.map do |k|
500
499
  next if k.start_with?('@')
501
500
  v = prop_get(k, include_defaults: false)
502
501
  next if v.nil?
@@ -508,6 +507,7 @@ end
508
507
  end
509
508
  if full_tree
510
509
  @subclasses.each do |tag_name, subclass|
510
+ next if tag_filter && !tag_filter.include?(tag_name)
511
511
  if subclass.is_a?(Hash)
512
512
  subclass.each do |k2, subclass2|
513
513
  tag_attr = k2.merge(subclass2.api_attributes.select { |attr, _| %w(uuid).include?(attr)})
@@ -625,14 +625,14 @@ my_prop = self._class.props[prop] or raise(InternalErrorException,
625
625
  "Unknown attribute for #{self._class}: #{prop}")
626
626
  @values[prop] = enforce_types(my_prop, value)
627
627
  end
628
- def to_xml(full_tree:, include_root:)
628
+ def to_xml(tag_filter: nil, full_tree:, include_root:)
629
629
  builder = Nokogiri::XML::Builder.new do |xml|
630
630
  xml.public_send(_section, begin
631
631
  selector
632
632
  rescue StandardError
633
633
  nil
634
634
  end) do
635
- xml_builder(xml, full_tree: full_tree)
635
+ xml_builder(xml, full_tree: full_tree, tag_filter: tag_filter)
636
636
  end
637
637
  end
638
638
  if include_root
@@ -652,12 +652,12 @@ element: xml_str
652
652
  @client.execute(payload)
653
653
  end
654
654
  alias :push! :edit!
655
- def set!
656
- xml_str = to_xml(full_tree: true, include_root: true)
655
+ def set!(tag_filter: nil)
656
+ xml_str = to_xml(full_tree: true, include_root: false, tag_filter: tag_filter)
657
657
  payload = {
658
658
  type: 'config',
659
659
  action: 'set',
660
- xpath: parent_instance.to_xpath,
660
+ xpath: to_xpath,
661
661
  element: xml_str
662
662
  }
663
663
  @client.execute(payload)
@@ -23659,6 +23659,66 @@ def has_multiple_values?; false; end
23659
23659
  def _section
23660
23660
  :entry
23661
23661
  end
23662
+ class CustomWidget < XML::ConfigClass
23663
+ def has_multiple_values?; true; end
23664
+ def _section
23665
+ :'custom-widget'
23666
+ end
23667
+ class Entry < ArrayConfigClass
23668
+ def has_multiple_values?; false; end
23669
+ def _section
23670
+ :entry
23671
+ end
23672
+ @props = {'@name'=>{'node-type'=>'attr-req', 'type'=>'string', 'maxlen'=>'31', 'help-string'=>'positive number', 'regex'=>'[1-9][0-9]*'}, 'predefined-report'=>{'node-type'=>'element', 'type'=>'string', 'maxlen'=>'63'}, 'custom-report'=>{'node-type'=>'element', 'type'=>'string', 'maxlen'=>'63'}, 'pdf-summary-report'=>{'node-type'=>'element', 'type'=>'string', 'maxlen'=>'63'}, 'log-view'=>{'node-type'=>'element', 'type'=>'string', 'maxlen'=>'63'}, 'csv'=>{'node-type'=>'element', 'type'=>'string', 'maxlen'=>'63'}}
23673
+ # positive number
23674
+ def name
23675
+ prop_get('@name')
23676
+ end
23677
+ def predefined_report
23678
+ prop_get('predefined-report')
23679
+ end
23680
+ def predefined_report=(val)
23681
+ prop_set('predefined-report', val)
23682
+ end
23683
+ def custom_report
23684
+ prop_get('custom-report')
23685
+ end
23686
+ def custom_report=(val)
23687
+ prop_set('custom-report', val)
23688
+ end
23689
+ def pdf_summary_report
23690
+ prop_get('pdf-summary-report')
23691
+ end
23692
+ def pdf_summary_report=(val)
23693
+ prop_set('pdf-summary-report', val)
23694
+ end
23695
+ def log_view
23696
+ prop_get('log-view')
23697
+ end
23698
+ def log_view=(val)
23699
+ prop_set('log-view', val)
23700
+ end
23701
+ def csv
23702
+ prop_get('csv')
23703
+ end
23704
+ def csv=(val)
23705
+ prop_set('csv', val)
23706
+ end
23707
+ end
23708
+ def selector_subclasses
23709
+ ['entry']
23710
+ end
23711
+ def entries
23712
+ return @subclasses['entry']
23713
+ end
23714
+ def entry(*args, &block)
23715
+ array_class_setter(*args, klass: Entry, section: 'entry', &block)
23716
+ end
23717
+ @props = {}
23718
+ end
23719
+ def custom_widget
23720
+ maybe_register_subclass('custom-widget', CustomWidget.new(parent_instance: self, client: @client, create_children: @create_children))
23721
+ end
23662
23722
  class All < XML::ConfigClass
23663
23723
  def has_multiple_values?; true; end
23664
23724
  def _section
@@ -23813,7 +23873,7 @@ end
23813
23873
  def variable
23814
23874
  maybe_register_subclass('variable', Variable.new(parent_instance: self, client: @client, create_children: @create_children))
23815
23875
  end
23816
- @props = {'@name'=>{'node-type'=>'attr-req', 'type'=>'string', 'maxlen'=>'63', 'subtype'=>'object-name', 'help-string'=>'alphanumeric string [ 0-9a-zA-Z._-]'}, 'title-page'=>{'node-type'=>'element', 'type'=>'bool', 'optional'=>'yes'}}
23876
+ @props = {'@name'=>{'node-type'=>'attr-req', 'type'=>'string', 'maxlen'=>'63', 'subtype'=>'object-name', 'help-string'=>'alphanumeric string [ 0-9a-zA-Z._-]'}, 'title-page'=>{'node-type'=>'element', 'type'=>'bool', 'optional'=>'yes'}, 'predefined'=>{'node-type'=>'element', 'type'=>'enum', 'enum'=>[{'value'=>'user-activity-report'}, {'value'=>'saas-application-usage-report'}]}}
23817
23877
  # alphanumeric string [ 0-9a-zA-Z._-]
23818
23878
  def name
23819
23879
  prop_get('@name')
@@ -23824,6 +23884,12 @@ end
23824
23884
  def title_page=(val)
23825
23885
  prop_set('title-page', val)
23826
23886
  end
23887
+ def predefined
23888
+ prop_get('predefined')
23889
+ end
23890
+ def predefined=(val)
23891
+ prop_set('predefined', val)
23892
+ end
23827
23893
  end
23828
23894
  def selector_subclasses
23829
23895
  ['entry']
@@ -75590,6 +75656,60 @@ def has_multiple_values?; false; end
75590
75656
  def _section
75591
75657
  :entry
75592
75658
  end
75659
+ class CustomWidget < XML::ConfigClass
75660
+ def has_multiple_values?; true; end
75661
+ def _section
75662
+ :'custom-widget'
75663
+ end
75664
+ class Entry < ArrayConfigClass
75665
+ def has_multiple_values?; false; end
75666
+ def _section
75667
+ :entry
75668
+ end
75669
+ @props = {'@name'=>{'node-type'=>'attr-req', 'type'=>'string', 'maxlen'=>'31', 'help-string'=>'positive number', 'regex'=>'[1-9][0-9]*'}, 'custom-report'=>{'node-type'=>'element', 'type'=>'string', 'maxlen'=>'63'}, 'pdf-summary-report'=>{'node-type'=>'element', 'type'=>'string', 'maxlen'=>'63'}, 'log-view'=>{'node-type'=>'element', 'type'=>'string', 'maxlen'=>'63'}, 'csv'=>{'node-type'=>'element', 'type'=>'string', 'maxlen'=>'63'}}
75670
+ # positive number
75671
+ def name
75672
+ prop_get('@name')
75673
+ end
75674
+ def custom_report
75675
+ prop_get('custom-report')
75676
+ end
75677
+ def custom_report=(val)
75678
+ prop_set('custom-report', val)
75679
+ end
75680
+ def pdf_summary_report
75681
+ prop_get('pdf-summary-report')
75682
+ end
75683
+ def pdf_summary_report=(val)
75684
+ prop_set('pdf-summary-report', val)
75685
+ end
75686
+ def log_view
75687
+ prop_get('log-view')
75688
+ end
75689
+ def log_view=(val)
75690
+ prop_set('log-view', val)
75691
+ end
75692
+ def csv
75693
+ prop_get('csv')
75694
+ end
75695
+ def csv=(val)
75696
+ prop_set('csv', val)
75697
+ end
75698
+ end
75699
+ def selector_subclasses
75700
+ ['entry']
75701
+ end
75702
+ def entries
75703
+ return @subclasses['entry']
75704
+ end
75705
+ def entry(*args, &block)
75706
+ array_class_setter(*args, klass: Entry, section: 'entry', &block)
75707
+ end
75708
+ @props = {}
75709
+ end
75710
+ def custom_widget
75711
+ maybe_register_subclass('custom-widget', CustomWidget.new(parent_instance: self, client: @client, create_children: @create_children))
75712
+ end
75593
75713
  class All < XML::ConfigClass
75594
75714
  def has_multiple_values?; true; end
75595
75715
  def _section
@@ -75744,7 +75864,7 @@ end
75744
75864
  def variable
75745
75865
  maybe_register_subclass('variable', Variable.new(parent_instance: self, client: @client, create_children: @create_children))
75746
75866
  end
75747
- @props = {'@name'=>{'node-type'=>'attr-req', 'type'=>'string', 'maxlen'=>'63', 'subtype'=>'object-name', 'help-string'=>'alphanumeric string [ 0-9a-zA-Z._-]'}, 'title-page'=>{'node-type'=>'element', 'type'=>'bool', 'optional'=>'yes'}}
75867
+ @props = {'@name'=>{'node-type'=>'attr-req', 'type'=>'string', 'maxlen'=>'63', 'subtype'=>'object-name', 'help-string'=>'alphanumeric string [ 0-9a-zA-Z._-]'}, 'title-page'=>{'node-type'=>'element', 'type'=>'bool', 'optional'=>'yes'}, 'predefined'=>{'node-type'=>'element', 'type'=>'enum', 'enum'=>[{'value'=>'user-activity-report'}, {'value'=>'saas-application-usage-report'}]}}
75748
75868
  # alphanumeric string [ 0-9a-zA-Z._-]
75749
75869
  def name
75750
75870
  prop_get('@name')
@@ -75755,6 +75875,12 @@ end
75755
75875
  def title_page=(val)
75756
75876
  prop_set('title-page', val)
75757
75877
  end
75878
+ def predefined
75879
+ prop_get('predefined')
75880
+ end
75881
+ def predefined=(val)
75882
+ prop_set('predefined', val)
75883
+ end
75758
75884
  end
75759
75885
  def selector_subclasses
75760
75886
  ['entry']
data/lib/palo_alto/op.rb CHANGED
@@ -75,63 +75,84 @@ module PaloAlto
75
75
  end
76
76
  end
77
77
 
78
- def xml_builder(xml, ops, obj)
79
- case obj
78
+ def xml_builder_iter(xml, ops, data)
79
+ raise 'No Ops?!' if ops.nil?
80
+
81
+ case data
80
82
  when String
81
- section = obj
82
- data = nil
83
- when Hash
84
- section = obj.keys.first
85
- data = obj[section]
83
+ section = data
84
+ data2 = nil
85
+ xml_builder(xml, ops, section, data2)
86
+ when Hash, Array
87
+ data.each do |section, data2|
88
+ xml_builder(xml, ops, section, data2)
89
+ end
86
90
  else
87
- raise obj.pretty_inspect
88
- end
89
-
90
- unless ops.key?(section.to_s)
91
- err = "Error #{section} does not exist. Valid: " + ops.keys.pretty_inspect
92
- raise err
91
+ raise data.pretty_inspect
93
92
  end
93
+ end
94
94
 
95
- ops_tree = ops[section.to_s]
96
-
97
- section = escape_xpath_tag(section)
95
+ def xml_builder(xml, ops, section, data, type = ops[section.to_s]&.[](:obj))
96
+ ops_tree = ops[section.to_s] || raise("no ops tree for section #{section}, #{ops.keys.inspect}")
97
+ # pp [:xml_builder, :section, section, :type, type]
98
+ # obj = data
98
99
 
99
- case ops_tree[:obj]
100
+ case type
100
101
  when :element
101
- xml.public_send(section, data)
102
+ xml.public_send(escape_xpath_tag(section), data)
102
103
  when :array
103
- xml.public_send(section) do
104
+ xml.public_send(escape_xpath_tag(section)) do
105
+ raise 'data is Hash and should be Array' if data.is_a?(Hash)
106
+
104
107
  data.each do |el|
105
108
  key = ops_tree.keys.first
106
- xml.public_send(escape_xpath_tag(key), el)
109
+ case el
110
+ when Hash
111
+ attr = ops_tree[key].find { |_k, v| v.is_a?(Hash) && v[:obj] == :'attr-req' }.first
112
+ xml.public_send(escape_xpath_tag(key), { attr => el[attr.to_sym] }) do
113
+ remaining_attrs = el.reject { |k, _v| k == attr.to_sym }
114
+
115
+ if remaining_attrs.any?
116
+ xml_builder(xml, ops_tree[key], remaining_attrs.keys.first.to_s, remaining_attrs.values.first,
117
+ :array)
118
+ end
119
+ end
120
+ when String
121
+ xml.public_send(key, el)
122
+ end
107
123
  end
108
124
  end
109
125
  when :sequence
110
- if data.nil?
111
- xml.send(section)
126
+ if data.nil? || data == true
127
+ xml.send(escape_xpath_tag(section))
112
128
  elsif data.is_a?(Hash)
113
- xml.send(section) do
114
- xml_builder(xml, ops_tree, data)
129
+ xml.send(escape_xpath_tag(section)) do
130
+ xml_builder_iter(xml, ops_tree, data)
115
131
  end
116
- else # array
132
+ else # array, what else could it be?!
133
+ raise "Unknown: #{attr.inspect}" unless data.is_a?(Array)
117
134
 
118
- if data.is_a?(Array)
119
- attr = data.find { |child| child.is_a?(Hash) && ops_tree[child.keys.first.to_s][:obj] == :'attr-req' }
120
- data.delete(attr)
121
- else
122
- attr = {}
123
- end
135
+ raise 'Too many hashes in an array, please update' if data.length > 1
136
+
137
+ key = ops_tree.keys.first
138
+ attr_name = ops_tree[key].find { |_k, v| v.is_a?(Hash) && v[:obj] == :'attr-req' }.first
139
+
140
+ hash = data.first.dup
141
+
142
+ data = [hash.reject { |k| k == attr_name.to_sym }]
143
+ attr = { attr_name => hash[attr_name.to_sym] }
124
144
 
125
- xml.public_send(section, attr) do
126
- data.each do |child|
127
- xml_builder(xml, ops_tree, child)
145
+ xml.public_send(escape_xpath_tag(section)) do
146
+ xml.public_send(escape_xpath_tag(key), attr) do
147
+ data.each do |child|
148
+ xml_builder_iter(xml, ops_tree[key], child)
149
+ end
128
150
  end
129
151
  end
130
152
  end
131
153
  when :union
132
- k, v = obj.first
133
- xml.send("#{k}_") do
134
- xml_builder(xml, ops_tree, v)
154
+ xml.public_send(escape_xpath_tag(section)) do
155
+ xml_builder_iter(xml, ops[section.to_s], data)
135
156
  end
136
157
  else
137
158
  raise ops_tree[:obj].pretty_inspect
@@ -141,7 +162,7 @@ module PaloAlto
141
162
 
142
163
  def to_xml(obj)
143
164
  builder = Nokogiri::XML::Builder.new do |xml|
144
- xml_builder(xml, @@ops, obj)
165
+ xml_builder_iter(xml, @@ops, obj)
145
166
  end
146
167
  builder.doc.root.to_xml
147
168
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PaloAlto
4
- VERSION = '0.5.0'
4
+ VERSION = '0.5.1'
5
5
  end
data/lib/palo_alto.rb CHANGED
@@ -129,6 +129,7 @@ module PaloAlto
129
129
  raise SessionTimedOutException
130
130
  when '400', '403'
131
131
  begin
132
+ pp [:error, options[:host], response.code, response.message]
132
133
  data = Nokogiri::XML.parse(response.body)
133
134
  message = data.xpath('//response/response/msg').text
134
135
  code = response.code.to_i
@@ -194,7 +195,7 @@ module PaloAlto
194
195
  new_xpath = 'response/result/' + search_xpath[(remove+2)..]
195
196
 
196
197
  results = cache.xpath(new_xpath)
197
- xml = Nokogiri.parse("<?xml version=\"1.0\"?><response><result>#{results.to_s}</result></response>")
198
+ xml = Nokogiri.parse("<?xml version=\"1.0\"?><response><result>#{results.to_s}</result></response>")
198
199
 
199
200
  if debug.include?(:statistics)
200
201
  warn "Elapsed for parsing cache: #{Time.now - start_time} seconds"
@@ -215,7 +216,7 @@ module PaloAlto
215
216
  options[:verify_ssl] = verify_ssl
216
217
  options[:payload] = payload
217
218
  options[:debug] = debug
218
- options[:timeout] = timeout || 180
219
+ options[:timeout] = timeout || 600
219
220
  options[:headers] = if payload[:type] == 'keygen'
220
221
  {}
221
222
  else
@@ -262,6 +263,7 @@ module PaloAlto
262
263
  rescue TemporaryException => e
263
264
  dont_retry_at = [
264
265
  'Partial revert is not allowed. Full system commit must be completed.',
266
+ 'Local commit jobs are queued. Revert operation is not allowed.',
265
267
  'Config for scope ',
266
268
  'Config is not currently locked for scope ',
267
269
  'Commit lock is not currently held by',
@@ -275,7 +277,7 @@ module PaloAlto
275
277
  max_retries = if dont_retry_at.any? { |str| e.message.start_with?(str) }
276
278
  0
277
279
  elsif e.message.start_with?('Timed out while getting config lock. Please try again.')
278
- 10
280
+ 30
279
281
  else
280
282
  1
281
283
  end
@@ -321,23 +323,31 @@ module PaloAlto
321
323
  cmd = if all
322
324
  'commit'
323
325
  else
324
- { commit: { partial: [
325
- { 'admin': admins },
326
- if device_groups
327
- device_groups.empty? ? 'no-device-group' : { 'device-group': device_groups }
328
- end,
329
- if templates
330
- templates.empty? ? 'no-template' : { 'template': templates }
331
- end,
332
- 'no-template-stack',
333
- 'no-log-collector',
334
- 'no-log-collector-group',
335
- 'no-wildfire-appliance',
336
- 'no-wildfire-appliance-cluster',
337
- { 'device-and-network': 'excluded' },
338
- { 'shared-object': 'excluded' }
339
- ].compact } }
326
+ commit_partial = {
327
+ 'no-template-stack': true,
328
+ 'no-log-collector': true,
329
+ 'no-log-collector-group': true,
330
+ 'no-wildfire-appliance': true,
331
+ 'no-wildfire-appliance-cluster': true,
332
+ 'device-and-network': 'excluded',
333
+ 'shared-object': 'excluded'
334
+ }
335
+
336
+ if device_groups
337
+ commit_partial.merge!(device_groups.empty? ? {'no-device-group': true} : { 'device-group': device_groups })
338
+ end
339
+
340
+ if templates
341
+ commit_partial.merge!(templates.empty? ? {'no-template': true} : { 'template': templates })
342
+ end
343
+
344
+ if admins
345
+ commit_partial.merge!({'admin': admins})
346
+ end
347
+
348
+ { commit: { partial: commit_partial } }
340
349
  end
350
+
341
351
  result = op.execute(cmd)
342
352
 
343
353
  return result if raw_result
@@ -358,7 +368,7 @@ module PaloAlto
358
368
  def primary_active?
359
369
  cmd = { show: { 'high-availability': 'state' } }
360
370
  state = op.execute(cmd)
361
- state.at_xpath('response/result/local-info/state').text == 'primary-active'
371
+ state.at_xpath('response/result/local-info/state')&.text == 'primary-active'
362
372
  end
363
373
 
364
374
  # area: config, commit
@@ -500,17 +510,17 @@ module PaloAlto
500
510
  cmd = if all
501
511
  { revert: 'config' }
502
512
  else
503
- { revert: { config: { partial: [
504
- { 'admin': [username] },
505
- 'no-template',
506
- 'no-template-stack',
507
- 'no-log-collector',
508
- 'no-log-collector-group',
509
- 'no-wildfire-appliance',
510
- 'no-wildfire-appliance-cluster',
511
- { 'device-and-network': 'excluded' },
512
- { 'shared-object': 'excluded' }
513
- ] } } }
513
+ { revert: { config: { partial: {
514
+ 'admin': [username],
515
+ 'no-template': true,
516
+ 'no-template-stack': true,
517
+ 'no-log-collector': true,
518
+ 'no-log-collector-group': true,
519
+ 'no-wildfire-appliance': true,
520
+ 'no-wildfire-appliance-cluster': true,
521
+ 'device-and-network': 'excluded',
522
+ 'shared-object': 'excluded'
523
+ } } } }
514
524
  end
515
525
 
516
526
  waited = 0
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.5.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Roesner
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-11-28 00:00:00.000000000 Z
11
+ date: 2024-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri