def monitor(self, pk, min_interval=1, max_interval=30, timeout=None, outfile=sys.stdout, **kwargs): """Monitor a running job. Blocks further input until the job completes (whether successfully or unsuccessfully) and a final status can be given. """ dots = itertools.cycle([0, 1, 2, 3]) longest_string = 0 interval = min_interval start = time.time() # Poll the Ansible Tower instance for status, and print the status # to the outfile (usually standard out). # # Note that this is one of the few places where we use `secho` # even though we're in a function that might theoretically be imported # and run in Python. This seems fine; outfile can be set to /dev/null # and very much the normal use for this method should be CLI # monitoring. result = self.status(pk, detail=True) last_poll = time.time() timeout_check = 0 while result['status'] != 'successful': # If the job has failed, we want to raise an Exception for that # so we get a non-zero response. if result['failed']: if is_tty(outfile) and not settings.verbose: secho('\r' + ' ' * longest_string + '\n', file=outfile) raise exc.JobFailure('Job failed.') # Sanity check: Have we officially timed out? # The timeout check is incremented below, so this is checking # to see if we were timed out as of the previous iteration. # If we are timed out, abort. if timeout and timeout_check - start > timeout: raise exc.Timeout('Monitoring aborted due to timeout.') # If the outfile is a TTY, print the current status. output = '\rCurrent status: %s%s' % (result['status'], '.' * next(dots)) if longest_string > len(output): output += ' ' * (longest_string - len(output)) else: longest_string = len(output) if is_tty(outfile) and not settings.verbose: secho(output, nl=False, file=outfile) # Put the process to sleep briefly. time.sleep(0.2) # Sanity check: Have we reached our timeout? # If we're about to time out, then we need to ensure that we # do one last check. # # Note that the actual timeout will be performed at the start # of the **next** iteration, so there's a chance for the job's # completion to be noted first. timeout_check = time.time() if timeout and timeout_check - start > timeout: last_poll -= interval # If enough time has elapsed, ask the server for a new status. # # Note that this doesn't actually do a status check every single # time; we want the "spinner" to spin even if we're not actively # doing a check. # # So, what happens is that we are "counting down" (actually up) # to the next time that we intend to do a check, and once that # time hits, we do the status check as part of the normal cycle. if time.time() - last_poll > interval: result = self.status(pk, detail=True) last_poll = time.time() interval = min(interval * 1.5, max_interval) # If the outfile is *not* a TTY, print a status update # when and only when we make an actual check to job status. if not is_tty(outfile) or settings.verbose: click.echo('Current status: %s' % result['status'], file=outfile) # Wipe out the previous output if is_tty(outfile) and not settings.verbose: secho('\r' + ' ' * longest_string, file=outfile, nl=False) secho('\r', file=outfile, nl=False) # Return the job ID and other response data answer = OrderedDict(( ('changed', True), ('id', pk), )) answer.update(result) # Make sure to return ID of resource and not update number # relevant for project creation and update answer['id'] = pk return answer