palo_alto 0.2.6 → 0.3.0

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.2.6'
4
+ VERSION = '0.3.0'
5
5
  end
data/lib/palo_alto.rb CHANGED
@@ -74,30 +74,35 @@ module PaloAlto
74
74
 
75
75
  module Helpers
76
76
  class Rest
77
+ @http_clients = {} # will include [http_client, lock]
78
+ @global_lock = Mutex.new
79
+
77
80
  def self.make_request(opts)
78
81
  options = {}
79
- options[:verify_ssl] = OpenSSL::SSL::VERIFY_PEER
80
- options[:timeout] = 60
82
+ options[:verify_ssl] ||= OpenSSL::SSL::VERIFY_PEER
81
83
 
82
- headers = {}
83
- headers['User-Agent'] = 'ruby-keystone-client'
84
- headers['Accept'] = 'application/xml'
85
- headers['Content-Type'] = 'application/x-www-form-urlencoded'
84
+ headers = {
85
+ 'User-Agent': 'ruby-keystone-client',
86
+ 'Accept': 'application/xml',
87
+ 'Content-Type': 'application/x-www-form-urlencoded'
88
+ }
86
89
 
87
90
  # merge in settings from method caller
88
91
  options = options.merge(opts)
89
92
  options[:headers].merge!(headers)
90
93
 
91
- thread = Thread.current
92
- unless thread[:http]
93
- thread[:http] = Net::HTTP.new(options[:host], options[:port])
94
- thread[:http].use_ssl = true
95
- thread[:http].verify_mode = options[:verify_ssl]
96
- thread[:http].read_timeout = thread[:http].open_timeout = options[:timeout]
97
- thread[:http].set_debug_output($stdout) if options[:debug].include?(:http)
98
- end
94
+ http_client = lock = nil
99
95
 
100
- thread[:http].start unless thread[:http].started?
96
+ @global_lock.synchronize do
97
+ unless (http_client, lock = @http_clients[options[:host]])
98
+ http_client = Net::HTTP.new(options[:host], 443)
99
+ http_client.use_ssl = true
100
+ http_client.verify_mode = options[:verify_ssl]
101
+ http_client.read_timeout = http_client.open_timeout = (options[:timeout] || 60)
102
+ http_client.set_debug_output(options[:debug].include?(:http) ? $stdout : nil)
103
+ @http_clients[options[:host]] = [http_client, (lock = Mutex.new)]
104
+ end
105
+ end
101
106
 
102
107
  payload = options[:payload]
103
108
  post_req = Net::HTTP::Post.new('/api/', options[:headers])
@@ -109,7 +114,11 @@ module PaloAlto
109
114
  post_req.set_form_data(payload)
110
115
  end
111
116
 
112
- response = thread[:http].request(post_req)
117
+ response = lock.synchronize do
118
+ http_client.start unless http_client.started?
119
+
120
+ http_client.request(post_req)
121
+ end
113
122
 
114
123
  case response.code
115
124
  when '200'
@@ -159,7 +168,7 @@ module PaloAlto
159
168
  end
160
169
 
161
170
  class XML
162
- attr_accessor :host, :port, :username, :password, :auth_key, :verify_ssl, :debug
171
+ attr_accessor :host, :username, :password, :auth_key, :verify_ssl, :debug, :timeout
163
172
 
164
173
  def execute(payload)
165
174
  retried = false
@@ -167,10 +176,10 @@ module PaloAlto
167
176
  # configure options for the request
168
177
  options = {}
169
178
  options[:host] = host
170
- options[:port] = port
171
179
  options[:verify_ssl] = verify_ssl
172
180
  options[:payload] = payload
173
181
  options[:debug] = debug
182
+ options[:timeout] = timeout || 180
174
183
  options[:headers] = if payload[:type] == 'keygen'
175
184
  {}
176
185
  else
@@ -189,6 +198,7 @@ module PaloAlto
189
198
 
190
199
  data = Nokogiri::XML.parse(text)
191
200
  unless data.xpath('//response/@status').to_s == 'success'
201
+ warn 'command failed'
192
202
  warn "sent:\n#{options.inspect}\n" if debug.include?(:sent_on_error)
193
203
  warn "received:\n#{text.inspect}\n" if debug.include?(:received_on_error)
194
204
  code = data.at_xpath('//response/@code')&.value.to_i # sometimes there is no code :( e.g. for 'op' errors
@@ -197,7 +207,6 @@ module PaloAlto
197
207
  end
198
208
 
199
209
  data
200
-
201
210
  rescue TemporaryException => e
202
211
  dont_retry_at = [
203
212
  'Partial revert is not allowed. Full system commit must be completed.',
@@ -206,27 +215,25 @@ module PaloAlto
206
215
  'Commit lock is not currently held by',
207
216
  'You already own a config lock for scope '
208
217
  ]
209
- if retried || dont_retry_at.any? { |x| e.message.start_with?(x) }
210
- raise e
211
- else
212
- warn "Got error #{e.inspect}; retrying" if debug.include?(:warnings)
213
- retried = true
214
- get_auth_key if e.is_a?(SessionTimedOutException)
215
- retry
216
- end
218
+ raise e if retried || dont_retry_at.any? { |x| e.message.start_with?(x) }
219
+
220
+ warn "Got error #{e.inspect}; retrying" if debug.include?(:warnings)
221
+ retried = true
222
+ get_auth_key if e.is_a?(SessionTimedOutException)
223
+ retry
217
224
  end
218
225
  end
219
226
 
220
- def commit!(all: false, device_groups: nil, wait_for_completion: true)
221
- return nil if device_groups.is_a?(Array) && device_groups.empty?
227
+ def commit!(all: false, device_groups: nil, templates: nil, wait_for_completion: true, wait: 5, timeout: 480)
228
+ return nil if device_groups.is_a?(Array) && device_groups.empty? && templates.is_a?(Array) && templates.empty?
222
229
 
223
230
  cmd = if all
224
231
  'commit'
225
232
  else
226
233
  { commit: { partial: [
227
234
  { 'admin': [username] },
228
- device_groups ? { 'device-group': device_groups } : nil,
229
- 'no-template',
235
+ device_groups ? ( device_groups.empty? ? 'no-device-group' : { 'device-group': device_groups } ) : nil,
236
+ templates ? ( templates.empty? ? 'no-template' : { 'template': templates } ) : nil,
230
237
  'no-template-stack',
231
238
  'no-log-collector',
232
239
  'no-log-collector-group',
@@ -236,12 +243,13 @@ module PaloAlto
236
243
  { 'shared-object': 'excluded' }
237
244
  ].compact } }
238
245
  end
239
- op.execute(cmd).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
246
+ result = op.execute(cmd)
247
+
248
+ job_id = result.at_xpath('response/result/job')&.text
249
+
250
+ return result unless job_id && wait_for_completion
251
+
252
+ wait_for_job_completion(job_id, wait: wait, timeout: timeout) if job_id
245
253
  end
246
254
 
247
255
  def full_commit_required?
@@ -289,7 +297,9 @@ module PaloAlto
289
297
  cmd = { request: { "#{area}-lock": { add: { comment: comment || '(null)' } } } }
290
298
  op.execute(cmd, type: type, location: location)
291
299
  true
292
- rescue PaloAlto::InternalErrorException
300
+ rescue PaloAlto::InternalErrorException => e
301
+ return true if e.message.start_with?('You already own a config lock for scope ')
302
+
293
303
  false
294
304
  end
295
305
  end
@@ -329,7 +339,7 @@ module PaloAlto
329
339
  }
330
340
  end
331
341
 
332
- def wait_for_job_completion(job_id, wait: 5, timeout: 300)
342
+ def wait_for_job_completion(job_id, wait: 5, timeout: 480)
333
343
  cmd = { show: { jobs: { id: job_id } } }
334
344
  start = Time.now
335
345
  loop do
@@ -343,7 +353,8 @@ module PaloAlto
343
353
  false
344
354
  end
345
355
 
346
- def revert!(all: false)
356
+ # wait: how long revert is retried (every 10 seconds)
357
+ def revert!(all: false, wait: 60)
347
358
  cmd = if all
348
359
  { revert: 'config' }
349
360
  else
@@ -359,16 +370,25 @@ module PaloAlto
359
370
  { 'shared-object': 'excluded' }
360
371
  ] } } }
361
372
  end
362
- op.execute(cmd)
373
+
374
+ waited = 0
375
+ begin
376
+ op.execute(cmd)
377
+ rescue StandardError => e
378
+ puts 'Revert failed; waiting and retrying'
379
+ sleep 10
380
+ waited += 1
381
+ retry while waited < wait
382
+ raise e
383
+ end
363
384
  end
364
385
 
365
- def initialize(host:, port:, username:, password:, debug: [])
366
- self.host = host
367
- self.port = port
368
- self.username = username
369
- self.password = password
370
- self.verify_ssl = OpenSSL::SSL::VERIFY_NONE
371
- self.debug = debug
386
+ def initialize(host:, username:, password:, verify_ssl: OpenSSL::SSL::VERIFY_NONE, debug: [])
387
+ self.host = host
388
+ self.username = username
389
+ self.password = password
390
+ self.verify_ssl = verify_ssl
391
+ self.debug = debug
372
392
 
373
393
  @subclasses = {}
374
394
 
@@ -377,8 +397,7 @@ module PaloAlto
377
397
  @arguments = [Expression.new(:this_node), []]
378
398
 
379
399
  # attempt to obtain the auth_key
380
- # raise 'Exception attempting to obtain the auth_key' if (self.class.auth_key = get_auth_key).nil?
381
- self.get_auth_key
400
+ get_auth_key
382
401
  end
383
402
 
384
403
  # Perform a query to the API endpoint for an auth_key based on the credentials provided
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.2.6
4
+ version: 0.3.0
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-12-02 00:00:00.000000000 Z
11
+ date: 2022-08-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri