Package: bladerunner
BladerunnerBladerunner is a program to send and receive information from any type
of ssh enabled text based device. It can run through a jumpbox if there
are networking restrictions. You can also provide an additional password
to use for any program after logging in, such as MySQL or sudo.
bladerunner will attempt to use the host password for everything unless
you specify otherwise, allowing it to default through sudo in simple use
cases. MySQL, FTP, and telnet prompts are included as well as the
default Ubuntu and CentOS bash shells and password prompts. You can
provide an additional prompt via command line arguments. bladerunner
will automatically accept SSH certificates and will throw ^C at any
command that exceeds the timeout before returning. Commands can be
loaded into a file and run from there line by line per host.InstallInstallation is done via the usual methods:$pythonsetup.pybuild$sudopythonsetup.pyinstallAlternatively, you can install via pip:$pipinstallbladerunnerRequiresPython (v2.7+), pexpect andfutures
2.1.3.OptionsFor a full list of options use:bladerunner--helpUsing a file with a list of commands in it is an easy way to execute
more complex tasks.Use of Bladerunner from within PythonIt may be useful to run Bladerunner from inside another script. Here’s
how:frombladerunner.baseimportBladerunnerfrombladerunner.formattingimportcsv_results,pretty_results,stacked_resultsdefbladerunner_test():"""A simple test of bladerunner's execution and output formats."""# pass in lists of strings, commands and hosts will be executed in orderservers=["testserver1.testdomain.com","testserver2.testdomain.com"]commands=["uptime","mysql","show databases;","exit;","date"]# this is the full options dictionaryoptions={"debug":False,"delay":None,"cmd_timeout":20,"csv_char":",","extra_prompts":["core-router1>"],"jump_host":"core-router1","jump_password":"cisco","jump_port":22,"jump_user":"admin","output_file":"/home/joebob/Documents/output.txt","passwd_prompts":[],# usually best to let Bladerunner decide"password":"hunter7","password_safety":True,"port":22,"progressbar":True,"second_password":"super-sekrets","shell_prompts":[],# this list is typically auto-generated"ssh":"ssh","ssh_key":None,"stacked":False,# preference flag for stacked results"style":0,"threads":100,"timeout":20,"unix_line_endings":False,"username":"joebob","width":80,# used in displaying results"windows_line_endings":False,# force the use of \r\n}# initialize Bladerunner with the options providedrunner=Bladerunner(options)# execution of commands on hosts, may take a while to returnresults=runner.run(commands,servers)# Prints CSV resultscsv_results(results)# Prints pretty_results using the available stylesforiinrange(4):options["style"]=ipretty_results(results,options)# Prints the results in a flat, vertically stacked waystacked_results(results)Threaded BladerunnerAs of Bladerunner 4.0.0 it is possible to use the run_threaded() method
to call the run() method in new thread. This is especially useful inside
of Tornado applications, which may need to be responsive in the main
thread during a long running task.It is recommended that you use gen.Task to do this inside of Tornado,
but Bladerunner itself simply returns a thread and calls a callback, so
it’s really up to the implementation as for how the threading is
handled. Here’s a simple use case for building a non-blocking remote
execution function:fromtornadoimportgen,webfrombladerunner.baseimportBladerunner@gen.enginedefthreaded_commands(options,commands,servers,callback=None):runner=Bladerunner(options)results=yieldgen.Task(runner.run_threaded,commands,servers)ifcallback:callback(results)classMyHandler(web.RequestHandler):@gen.enginedefget(self,*args,**kwargs):commands=self.qs_dict.get("commands",[])servers=self.qs_dict.get("servers",[])ifcommandsandservers:# password can be a list to try multiple passwords per hostoptions={"username":"root","password":["r00t","d3f4ult"]}results=yieldgen.Task(threaded_commands,options,commands,servers)self.write(200,results)else:self.write(404,"commands or servers not provided in qs_dict")Bladerunner InteractiveSometimes, you need to apply logic to conditionally decide commands to
issue based off of the results of a previous command. As of Bladerunner
4.1.0 there are now a couple different ways you can do this.Single host interactive via python shellHere is the simplest use case of a BladerunnerInteractive object:>>>frombladerunnerimportBladerunner>>>runner=Bladerunner()>>>inter=runner.interactive("some_host")>>>inter.run("uptime")'17:46:22 up 23 days, 19:52,  6 users,  load average: 0.17, 0.13, 0.09'Multiple hosts interactively via python shellRather than handling the BladerunnerInteractive objects yourself, you
can store them in the base Bladerunner object instead, letting the base
object run the interactive command on all hosts in parallel. An example:>>>frombladerunnerimportBladerunner>>>runner=Bladerunner()>>>runner.run_interactive("hostname","some_host")some_host:some_host>>>runner.run_interactive("hostname")some_host:some_host>>>runner.run_interactive("hostname","some_other_host")some_host:some_hostsome_other_host:some_other_hostAs you can see, supplying more hosts (the second argument, can also be a
list), is optional. If you do supply more hosts, they will be added to
the internal list. To remove a host from the pool, use
Bladerunner.end_interactive() with the hostname or list of hostnames
you’d like to remove:>>>runner.end_interactive("some_host")>>>runner.interactive_hosts{'some_other_host':<BladerunnerInteractiveobjectconnectedto'some_other_host'at0xb6f1dd8c>}Interactive ThreadingBoth the run and the connect methods of the BladerunnerInteractive
objects can be threaded. When using the base object’s run_interactive
method, it will use multi-threading internally to perform the action on
all devices in parallel, but the call itself is blocking. To work around
this, you need to use the BladerunnerInteractive objects themselves. An
example of threaded connecting and threaded interactive command running:fromtornadoimportgenfrombladerunnerimportBladerunneroptions={}runner=Bladerunner(options)inter=runner.interactive("somewhere")connected=yieldgen.Task(inter.connect_threaded)ifconnected:results=yieldgen.Task(inter.run_threaded,"whoami")if"root"inresults:print("god-mode is enabled")else:print("{}is but a mere plebeian".format(results))else:print("could not connect")You do not need to make a specific call to connect_threaded, as the run
call will detect that it hasn’t connected yet and attempt to. However,
it may be preferred to know the connection status earlier.Predefined Interactive FunctionsIn the instance where you know exactly what you’re looking for, and
exactly what to do based off of that outcome, it may be easiest to write
a BladerunnerInteractive function and let the base object do the
threading for you. In this way, we can run the same logic against many
hosts. An example script where you need to check the running status of a
service and issue a restart on any hosts where the service is currently
down:frombladerunnerimportBladerunnerdefmy_function(session):"""You can call this anything, but the signature has to be exact.

    You must accept a single non-keyword argument, which will be the
    BladerunnerInteractive object.

    You can return anything you want, anything other than None will be
    returned grouped as a list with all the other function calls.
    """results=session.run("/etc/init.d/httpd status")ifnot"is running..."inresults:session.run("/etc/init.d/httpd restart")returnsession.serverdefmain():runner=Bladerunner({"username":"root"})res=runner.run_interactive_function(my_function,["host1","host2"])print("restarted httpd service on:{}".format(", ".join(res)))if__name__=="__main__":main()In the case where you need different connection parameters for multiple
sets of devices, make more Bladerunner base objects and spawn the
interactive sets off of them. Alternatively, you can call an update on
the base object’s options, like so:frombladerunnerimportBladerunnerdefmy_function():results=session.run("/etc/init.d/httpd status")ifnot"is running..."inresults:session.run("/etc/init.d/httpd restart")returnsession.server,Truereturnsession.server,Falserunner=Bladerunner({"username":"user1","password":"password1"})# line separated lists of hostnames or IPs can be passed as string filepathsrunner.run_interactive_function(my_function,"/root/server_list_1")# if you want to end these sessions, remove them from the base object:runner.end_interactive("/root/server_list_1")# new BladerunnerInteractive objects inherit the base object's settings# but you can update them on the base rather than having to make new onesrunner.options.update({"username":"user2","password":"password2"})# the connections to these servers will be maintained in the base object# indefinately! there are also automatic re-connect methods that are used.# if you need finer grained control of the sessions, you can pool them# externally to enforce timeouts and/or keepalives.results=runner.run_interactive_function(my_function,"/root/server_list_2")# results at this point is whatever we've defined to return in our function,# inside a list with each function run per host (order not guaranteed).forserver_name,httpd_restartedinresults:print("httpd on server{}was{}restarted!".format(server_name,"not "*int(httpd_restartedisFalse),))Non-Standard SSHAs of Bladerunner 4.1.8+ you can provide –ssh to give a non-standard
command as ssh. This will clear any automatically added flags, so include
them in your command if any are required. Also keep in mind if you are
also using Bladerunner with a jumpbox, the ssh command needs to be
available there as well.As a usage example for this, Bladerunner 4.1.8+ can be used with the gcloud CLI:$bladerunner-nN--ssh="gcloud compute ssh""echo 'hello world'"$(kubectlgetnodes-oname|cut-d'/'-f2|tr'\n'' ')Bugs & TODOIf you come across a bug, please create a new issue in theissue
tracking systemwith
enough relevant details and it will be dealt with promptly.Authoritative SourceNote thatthis repositoryis the
source repository for the Python Packaging Index and is the upstream repository
for all bug fixes and feature development.This repository is distributed under the GPLv2 license, with the acknowledgement
that some (most, for now) of the library source code is under the BSD license
and is still Copyright (c) 2015 Activision Publishing, Inc (see the LICENSE file
for full details).Having said that, this project is transitioning away from the prior codebase.
To track how much of the code base is GPLv2 vs BSD, you can use:$gitdiff--stat62d52e04bb86614efc3e6e280b2c9adccddde83fmasterIt’s also being tracked and updated via Travis-CI right here: