Skip to content

Using Nexpose::Wait

Gavin Schneider edited this page Jan 3, 2018 · 7 revisions

Introduction

The Nexpose::Wait class is meant to be an easy way to add a few lines to your scripts in order to make sure certain actions have completed prior to moving on to the next steps within your task. It has built in methods for common actions, and a custom option for more specific needs.

The Nexpose::Wait uses the keyword arguments feature in the Ruby language for calling a method, so it might look a bit odd at first, but using keyword arguments should be more convenient overall.

wait = Nexpose::Wait.new(retry_count: 5, timeout: 60, polling_interval: 5)

At this point you've instantiated a new Wait object which will try to evaluate every five seconds, for a total of 300 seconds (60 second timeout, retrying five total times - 60 seconds multiplied by 5).

Since we are using keyword arguments, you can initialize retry_count, timeout and polling_interval in any order. And if you want to use just the default values, you do not need to include any arguments. The following code uses just the default values.

wait = Nexpose::Wait.new

Default Values:

  • retry_count 0
  • timeout 120 seconds
  • polling_interval 1 second

Applying

There are now three methods which are available:

wait.for_report

wait.for_integration

wait.for_judgment

wait.for_report will allow you to wait for a report to finish, and allow you to act once the timeout has been reached. Similar to how you initialized the object, all the methods also use Ruby's keyword arguments.

wait.for_report(nexpose_connection: nsc, report_id: 1)

This will use the wait we initialized above, and we pass in our nsc connection object, and the ID of the report we want to wait on. This will wait for the report status to 'Generated'.

wait.for_integration will allow you to wait for a scan to be at a certain state before moving on.

wait.for_integration(nexpose_connection: nsc, scan_id: 1, status: 'finished')

Common statuses would be finished, stopped, running, integrating or paused. Whichever status our heart desires we can just have the wait.for_integration keep asking Nexpose the status of that particular scan.

wait.for_judgment is a bit of a wildcard, because instead of the method looking for something Nexpose specific, we can have this wait do whatever we need. One example might be, we have kicked off a few small scans and we want to wait for them all to finish.

wait.for_judgment( proc: -> { nsc.activity.empty?}, desc: "Waiting for All Scan Activity to be Complete")

For this method, we create a proc that uses the nsc.activity method to tell us if any scans are running. The proc can literally be any condition that we want to wait until is true.

wait.for_judgment( proc: -> {1 == 1}, desc: "Waiting for one to equal one...")

Above is a silly example, but it's to show that we do not need to use a Nexpose function inside of our wait.for_judgment method. Another example might be you have another tool running along side your script, and you would like to wait for an action within that tool to finish prior to doing something within Nexpose.

wait.for_judgment( proc: -> {Tool::Status == 'Ready'}, desc: "Waiting for external tool to finish.")

We use the desc: argument to tell us what this judgment is judging. This will be more clear when we look at getting the error message when our wait fails.

Error Handling

Now that we've setup our wait how do we know that it actually worked? We can know if the wait finished without error, by using wait.ready?. Also the error message is available with the type of error that was encountered, wait.error_message. Note: wait.error_messagealways has a value, and you should only rely on wait.ready? to know if your wait successfully completed. A common full example is shown below.

wait = Nexpose::Wait.new(retry_count: 5, timeout: 60, polling_interval: 5)
wait.for_report(nexpose_connection: nsc, report_id: 1)
raise wait.error_message unless wait.ready?

Reuse

The Nexpose::Wait class has the ability for the same instance of the object to be reused. This might be helpful if you want to define one global set of default timeouts and polling intervals, without having to instantiate a new wait object. For example, if you have a script that will generate a bunch of reports, and you want to wait for all of them, you do not necessarily need to re-instantiate your wait.

wait = Nexpose::Wait.new(retry_count: 5, timeout: 60, polling_interval: 5)
wait.for_report(nexpose_connection: nsc, report_id: 1)
raise wait.error_message unless wait.ready?

wait.for_report(nexpose_connection: nsc, report_id: 2)
raise wait.error_message unless wait.ready?

wait.for_report(nexpose_connection: nsc, report_id: 3)
raise wait.error_message unless wait.ready?

In the above example we just called Nexpose::Wait.new only once, but we were able to reuse the same object for each of our different report_ids. We can also reuse the same object and use any of the different .for_ methods with the same result.

wait = Nexpose::Wait.new(retry_count: 5, timeout: 60, polling_interval: 5)
wait.for_report(nexpose_connection: nsc, report_id: 1)
raise wait.error_message unless wait.ready?

wait.for_integration(nexpose_connection: nsc, scan_id: 1, status: 'finished')
raise wait.error_message unless wait.ready?

wait.for_judgment( proc: -> { nsc.activity.empty?}, desc: "Waiting for All Scan Activity to be Complete")
raise wait.error_message unless wait.ready?